import {
  DateTime
} from './lib/date'
import {
  BaseMod
} from './mod/base'
import {
  UniCrypto
} from './lib/uni-crypto'
// 应用
export class App extends BaseMod {
  constructor () {
    super()
    this.tableName = 'app-list'
  }

  // 获取应用信息
  async getAppByAppid (appId) {
    const appInfo = await this.getCollection(this.tableName).where({
      appid: appId
    }).limit(1).get()

    return appInfo.data.length > 0 ? appInfo.data[0] : []
  }

  // 获取应用_id
  async getAppIdByAppid (appId) {
    const app = await this.getAppByAppid(appId)
    return app.length > 0 ? app._id : ''
  }
}

// 版本
export class Version extends BaseMod {
  constructor () {
    super()
    this.tableName = 'stat-app-versions'
  }

  // 获取版本信息
  async getVersion (appid, platformId, appVersion) {
    const cacheKey = 'uni-stat-version-' + appid + '-' + platformId + '-' + appVersion
    let versionData = await this.getCache(cacheKey)
    if (!versionData) {
      const versionInfo = await this.getCollection(this.tableName).where({
        appid: appid,
        platform_id: platformId,
        version: appVersion
      }).limit(1).get()
      versionData = []
      if (versionInfo.data.length > 0) {
        versionData = versionInfo.data[0]
        await this.setCache(cacheKey, versionData)
      }
    }
    return versionData
  }

  // 获取版本信息没有则进行创建
  async getVersionAndCreate (appid, platformId, appVersion) {
    const versionInfo = await this.getVersion(appid, platformId, appVersion)
    if (versionInfo.length === 0) {
      if (appVersion.length > 0 && !appVersion.includes('}')) {
        const thisTime = new DateTime().getTime()
        const insertParam = {
          appid: appid,
          platform_id: platformId,
          version: appVersion,
          create_time: thisTime,
          update_time: thisTime
        }
        const res = await this.insert(this.tableName, insertParam)
        if (res && res.id) {
          return Object.assign(insertParam, {
            _id: res.id
          })
        }
      }
    }
    return versionInfo
  }

  // 获取版本_id
  async getVersionId (appid, platformId, appVersion) {
    const versionInfo = await this.getVersion(appid, platformId, appVersion)
    return versionInfo.length > 0 ? versionInfo._id : ''
  }
}

// 平台
export class Platform extends BaseMod {
  constructor () {
    super()
    this.tableName = 'app-platforms'
  }

  // 获取平台信息
  async getPlatform (platform, os) {
    const cacheKey = 'uni-stat-platform-' + platform + '-' + os
    let platformData = await this.getCache(cacheKey)
    if (!platformData) {
      const platformCode = this.getPlatformCode(platform, os)
      const platformInfo = await this.getCollection(this.tableName).where({
        code: platformCode
      }).limit(1).get()
      platformData = []
      if (platformInfo.data.length > 0) {
        platformData = platformInfo.data[0]
        await this.setCache(cacheKey, platformData)
      }
    }
    return platformData
  }

  // 获取平台信息没有则创建
  async getPlatformAndCreate (platform, os) {
    if (!platform) {
      return false
    }
    const platformInfo = await this.getPlatform(platform, os)

    if (platformInfo.length === 0) {
      const platformCode = this.getPlatformCode(platform, os)
      const insertParam = {
        code: platformCode,
        name: platformCode,
        create_time: new DateTime().getTime()
      }
      const res = await this.insert(this.tableName, insertParam)
      if (res && res.id) {
        return Object.assign(insertParam, {
          _id: res.id
        })
      }
    }
    return platformInfo
  }

  // 获取平台_id
  async getPlatformId (platform, os) {
    const platformInfo = await this.getPlatform(platform, os)
    return platformInfo.length > 0 ? platformInfo._id : ''
  }

  // 获取平台代码
  getPlatformCode (platform, os) {
    let platformCode = platform
    if (platform === 'n') {
      if (os === 'i') {
        platformCode = 'ios'
      } else {
        platformCode = 'android'
      }
    }
    return platformCode
  }
}

// 场景值
export class Scenes extends BaseMod {
  constructor () {
    super()
    this.tableName = 'mp-scenes'
  }

  async getScenes (platform, code) {
    const cacheKey = 'uni-stat-scenes-' + platform + '-' + code
    let scenesData = await this.getCache(cacheKey)
    if (!scenesData) {
      const scenesInfo = await this.getCollection(this.tableName).where({
        platform: platform,
        scene_code: code
      }).limit(1).get()
      scenesData = []
      if (scenesInfo.data.length > 0) {
        scenesData = scenesInfo.data[0]
        await this.setCache(cacheKey, scenesData)
      }
    }
    return scenesData
  }

  async getScenesByPlatformId (platformId, code) {
    const platform = new Platform()
    let platformInfo = await this.getCollection(platform.tableName).where({
      _id: platformId
    }).limit(1).get()
    let scenesData
    if (platformInfo.data.length > 0) {
      platformInfo = platformInfo.data[0]
      scenesData = await this.getScenes(platformInfo.code, code)
    } else {
      scenesData = []
    }
    return scenesData
  }

  async getScenesName (platform, code) {
    const scenesData = await this.getScenes(platform, code)
    if (scenesData.length === 0) {
      return ''
    }
    return scenesData.scene_name
  }

  async getScenesNameByPlatformId (platformId, code) {
    const scenesData = await this.getScenesByPlatformId(platformId, code)
    if (scenesData.length === 0) {
      return ''
    }
    return scenesData.scene_name
  }
}

// 渠道
export class Channel extends BaseMod {
  constructor () {
    super()
    this.tableName = 'app-channels'
    this.scenes = new Scenes()
  }

  // 获取渠道信息
  async getChannel (appid, platformId, channel) {
    const cacheKey = 'uni-stat-channel-' + appid + '-' + platformId + '-' + channel
    let channelData = await this.getCache(cacheKey)
    if (!channelData) {
      const channelInfo = await this.getCollection(this.tableName).where({
        appid: appid,
        platform_id: platformId,
        channel_code: channel
      }).limit(1).get()
      channelData = []
      if (channelInfo.data.length > 0) {
        channelData = channelInfo.data[0]
        if (channelData.channel_name === '') {
          const scenesName = await this.scenes.getScenesNameByPlatformId(platformId, channel)
          if (scenesName) {
            await this.update(this.tableName, { channel_name: scenesName, update_time: new DateTime().getTime() }, { _id: channelData._id })
          }
        }
        await this.setCache(cacheKey, channelData)
      }
    }
    return channelData
  }

  // 获取渠道信息没有则进行创建
  async getChannelAndCreate (appid, platformId, channel) {
    if (!appid || !platformId) {
      return []
    }

    const channelInfo = await this.getChannel(appid, platformId, channel)
    if (channelInfo.length === 0) {
      const thisTime = new DateTime().getTime()
      const insertParam = {
        appid: appid,
        platform_id: platformId,
        channel_code: channel,
        channel_name: await this.scenes.getScenesNameByPlatformId(platformId, channel),
        create_time: thisTime,
        update_time: thisTime
      }
      const res = await this.insert(this.tableName, insertParam)
      if (res && res.id) {
        return Object.assign(insertParam, {
          _id: res.id
        })
      }
    }
    return channelInfo
  }

  // 获取渠道_id
  async getChannelId (appid, platformId, channel) {
    const channelInfo = await this.getChannel(appid, platformId, channel)
    return channelInfo.length > 0 ? channelInfo._id : ''
  }

  // 获取渠道码或者场景值
  getChannelCode (params) {
    // web默认无渠道
    if (params.ut === 'h5') {
      return ''
    }

    if (params.ch) {
      return params.ch
    } else if (params.sc) {
      return params.sc
    }
    return ''
  }
}

// 页面
export class Page extends BaseMod {
  constructor () {
    super()
    this.tableName = 'stat-pages'
  }

  // 获取页面信息
  async getPage (appid, url) {
    const cacheKey = 'uni-stat-page-' + appid + '-' + url
    let pageData = await this.getCache(cacheKey)
    if (!pageData) {
      const pageInfo = await this.getCollection(this.tableName).where({
        appid: appid,
        path: url
      }).limit(1).get()
      pageData = []
      if (pageInfo.data.length > 0) {
        pageData = pageInfo.data[0]
        await this.setCache(cacheKey, pageData)
      }
    }
    return pageData
  }

  // 获取页面信息不存在则创建
  async getPageAndCreate (appid, url, title) {
    if (!url) {
      return false
    }
    const baseurl = this.getBaseUrl(url)
    const pageInfo = await this.getPage(appid, baseurl)
    if (pageInfo.length === 0) {
      const thisTime = new DateTime().getTime()
      const insertParam = {
        appid: appid,
        path: baseurl,
        title: title,
        page_params: [],
        create_time: thisTime,
        update_time: thisTime
      }
      const res = await this.insert(this.tableName, insertParam)

      if (res && res.id) {
        return Object.assign(insertParam, {
          _id: res.id
        })
      }
    } else if (!pageInfo.title && title) {
      const cacheKey = 'uni-stat-page-' + appid + '-' + baseurl
      await this.clearCache(cacheKey)
      await this.update(this.tableName, {
        title: title
      }, {
        _id: pageInfo._id
      })
    }

    return pageInfo
  }

  // 获取页面_id
  async getPageId (appid, url) {
    const pageInfo = await this.getPage(appid, url)
    return pageInfo.length > 0 ? pageInfo._id : ''
  }

  getBaseUrl (url) {
    let baseurl = url.split('?')[0]
    if (baseurl !== '/' && baseurl.indexOf('/') === 0) {
      baseurl = baseurl.substr(1)
    }
    return baseurl
  }
}

export class StatEvent extends BaseMod {
  constructor () {
    super()
    this.tableName = 'stat-events'
    this.defaultEvent = this.getConfig('event') || {
      login: '登录',
      register: '注册',
      click: '点击',
      share: '分享',
      pay_success: '支付成功',
      pay_fail: '支付失败'
    }
  }

  // 获取页面信息
  async getEvent (appid, eventKey) {
    const cacheKey = 'uni-stat-event-' + appid + '-' + eventKey
    let eventData = await this.getCache(cacheKey)
    if (!eventData) {
      const eventInfo = await this.getCollection(this.tableName).where({
        appid: appid,
        event_key: eventKey
      }).get()
      eventData = []
      if (eventInfo.data.length > 0) {
        eventData = eventInfo.data[0]
        await this.setCache(cacheKey, eventData)
      }
    }
    return eventData
  }

  // 获取页面信息不存在则创建
  async getEventAndCreate (appid, eventKey) {
    const eventInfo = await this.getEvent(appid, eventKey)
    if (eventInfo.length === 0) {
      const thisTime = new DateTime().getTime()
      const insertParam = {
        appid: appid,
        event_key: eventKey,
        event_name: this.defaultEvent[eventKey] ?? '',
        create_time: thisTime,
        update_time: thisTime
      }
      const res = await this.insert(this.tableName, insertParam)

      if (res && res.id) {
        return Object.assign(insertParam, {
          _id: res.id
        })
      }
    }

    return eventInfo
  }
}

// 会话日志
export class SessionLog extends BaseMod {
  constructor () {
    super()
    this.tableName = 'stat-session-logs'
  }

  async batchFill (reportParams) {
    let params, pageInfo, nowTime, firstVistTime, lastVistTime; const fillParams = []

    const page = new Page()
    const platform = new Platform()
    const dateTime = new DateTime()
    const channel = new Channel()
    for (const pk in reportParams) {
      params = reportParams[pk]
      // 应用信息
      if (!params.ak) {
        continue
      }

      // 平台信息
      if (!params.ut) {
        continue
      }

      // 设备信息
      if (!params.uuid) {
        continue
      }

      // 页面信息
      pageInfo = await page.getPageAndCreate(params.ak, params.url, params.ttpj)
      if (!pageInfo || pageInfo.length === 0) {
        continue
      }

      nowTime = dateTime.getTime()
      firstVistTime = params.fvts ? dateTime.strToTime(params.fvts) : nowTime
      lastVistTime = params.lvts ? dateTime.strToTime(params.lvts) : 0
      fillParams.push({
        appid: params.ak,
        version: params.v ?? '',
        platform: platform.getPlatformCode(params.ut, params.p),
        channel: channel.getChannelCode(params),
        // 访问者
        uuid: params.uuid,
        is_first_visit: (!lastVistTime || firstVistTime === lastVistTime) ? 1 : 0,
        first_visit_time: firstVistTime,
        last_visit_time: nowTime,
        visit_count: params.tvc ? parseInt(params.tvc) : 1,
        // 页面相关
        entry_page_id: pageInfo._id,
        exit_page_id: pageInfo._id,
        page_count: 0,
        event_count: 0,
        duration: 1,
        // 版本
        sdk_version: params.mpsdk ?? '',
        platform_version: params.mpv ?? '',
        // 设备相关
        device_os_name: (params.p && params.p === 'i') ? 'ios' : 'android',
        device_os_version: params.sv ?? '',
        device_vendor: params.brand ?? '',
        device_model: params.md ?? '',
        device_language: params.lang ?? '',
        device_pixel_ratio: params.pr ?? '',
        device_window_width: params.ww ?? '',
        device_window_height: params.wh ?? '',
        device_screen_width: params.sw ?? '',
        device_screen_height: params.sh ?? '',
        // 地区相关
        location_ip: '',
        location_latitude: params.lat ? parseFloat(params.lat) : 0,
        location_longitude: params.lng ? parseFloat(params.lng) : 0,
        location_country: params.cn ?? '',
        location_province: params.pn ?? '',
        location_city: params.ct ?? '',
        is_finish: 0,
        create_time: nowTime
      })
    }

    if (fillParams.length === 0) {
      console.log('No session params')
      return {
        code: 200,
        msg: 'Invild param'
      }
    }

    return await this.batchInsert(this.tableName, fillParams)
  }

  // 日志填充
  async fill (params) {
    // 应用信息
    if (!params.ak) {
      return {
        code: 200,
        msg: 'Parameter "ak" not found'
      }
    }

    // 平台信息
    if (!params.ut) {
      return {
        code: 200,
        msg: 'Parameter "ut" not found'
      }
    }

    // 设备信息
    if (!params.uuid) {
      return {
        code: 200,
        msg: 'Parameter "uuid" not found'
      }
    }

    // 页面信息
    const page = new Page()
    const pageInfo = await page.getPageAndCreate(params.ak, params.url, params.ttpj)
    if (!pageInfo || pageInfo.length === 0) {
      return {
        code: 300,
        msg: 'Not found this entry page'
      }
    }
    if (this.debug) {
      console.log('pageInfo', JSON.stringify(pageInfo))
    }
    const platform = new Platform()
    const dateTime = new DateTime()
    const channel = new Channel()
    const nowTime = dateTime.getTime()
    const firstVistTime = params.fvts ? dateTime.strToTime(params.fvts) : nowTime
    const lastVistTime = params.lvts ? dateTime.strToTime(params.lvts) : 0
    const fillParams = {
      appid: params.ak,
      version: params.v ?? '',
      platform: platform.getPlatformCode(params.ut, params.p),
      channel: channel.getChannelCode(params),
      // 访问者
      uuid: params.uuid,
      is_first_visit: (!lastVistTime || firstVistTime === lastVistTime) ? 1 : 0,
      first_visit_time: firstVistTime,
      last_visit_time: nowTime,
      visit_count: params.tvc ? parseInt(params.tvc) : 1,
      // 页面相关
      entry_page_id: pageInfo._id,
      exit_page_id: pageInfo._id,
      page_count: 0,
      event_count: 0,
      duration: 1,
      // 版本
      sdk_version: params.mpsdk ?? '',
      platform_version: params.mpv ?? '',
      // 设备相关
      device_os_name: (params.p && params.p === 'i') ? 'ios' : 'android',
      device_os_version: params.sv ?? '',
      device_vendor: params.brand ?? '',
      device_model: params.md ?? '',
      device_language: params.lang ?? '',
      device_pixel_ratio: params.pr ?? '',
      device_window_width: params.ww ?? '',
      device_window_height: params.wh ?? '',
      device_screen_width: params.sw ?? '',
      device_screen_height: params.sh ?? '',
      // 地区相关
      location_ip: '',
      location_latitude: params.lat ? parseFloat(params.lat) : 0,
      location_longitude: params.lng ? parseFloat(params.lng) : 0,
      location_country: params.cn ?? '',
      location_province: params.pn ?? '',
      location_city: params.ct ?? '',
      is_finish: 0,
      create_time: nowTime
    }

    const res = await this.insert(this.tableName, fillParams)

    if (res && res.id) {
      return {
        code: 0,
        msg: 'success',
        data: {
          pageId: pageInfo._id,
          sessionLogId: res.id,
          entryPageId: fillParams.entry_page_id,
          eventCount: fillParams.event_count,
          startTime: fillParams.first_visit_time,
          createTime: fillParams.create_time,
          pageCount: fillParams.page_count
        }
      }
    } else {
      return {
        code: 500,
        msg: 'Filled error'
      }
    }
  }

  async getSessionLog (params) {
    // 页面信息
    const page = new Page()
    const pageInfo = await page.getPageAndCreate(params.ak, params.url, params.ttpj)
    if (!pageInfo || pageInfo.length === 0) {
      return {
        code: 300,
        msg: 'Not found this entry page'
      }
    }

    const platformObj = new Platform()
    const platform = platformObj.getPlatformCode(params.ut, params.p)
    // 查询日志
    const sessionLogInfo = await this.getCollection(this.tableName).where({
      appid: params.ak,
      platform: platform,
      uuid: params.uuid,
      is_finish: 0
    }).orderBy('create_time', 'desc').limit(1).get()

    if (sessionLogInfo.data.length > 0) {
      const sessionLogInfoData = sessionLogInfo.data[0]
      // 最后一次访问时间距现在超过半小时算一次新的会话
      const sessionTime = new DateTime().getTime() - sessionLogInfoData.last_visit_time
      if (sessionTime >= 1800000) {
        if (this.debug) {
          console.log('session log time expired', sessionTime)
        }
        await this.update(this.tableName, {
          is_finish: 1
        }, {
          appid: params.ak,
          platform: platform,
          uuid: params.uuid,
          is_finish: 0
        })
        return await this.fill(params)
      } else {
        return {
          code: 0,
          msg: 'success',
          data: {
            pageId: pageInfo._id,
            sessionLogId: sessionLogInfoData._id,
            entryPageId: sessionLogInfoData.entry_page_id,
            eventCount: sessionLogInfoData.event_count,
            startTime: sessionLogInfoData.first_visit_time,
            createTime: sessionLogInfoData.create_time,
            pageCount: sessionLogInfoData.page_count
          }
        }
      }
    } else {
      return await this.fill(params)
    }
  }

  // 清理数据
  async clean (days) {
    days = Math.max(parseInt(days), 1)
    console.log('clean session logs - day:', days)

    const dateTime = new DateTime()
    const res = await this.delete(this.tableName, {
      create_time: {
        $lt: dateTime.getTimeBySetDays(0 - days)
      }
    })

    if (!res.code) {
      console.log('clean session log:', res)
    }
    return res
  }
}

// 页面日志
export class PageLog extends BaseMod {
  constructor () {
    super()
    this.tableName = 'stat-page-logs'
    this.sessionLogInfo = []
  }

  // 日志填充
  async fill (reportParams) {
    let params; let sessionKey, sessionLogKey; let sessionLogInfo; const sessionData = []; const pageData = []; let pageKey; let pageInfo; let referPageInfo; const fillParams = []
    const sessionLog = new SessionLog()
    const page = new Page()
    const platform = new Platform()
    const dateTime = new DateTime()
    const channel = new Channel()
    for (const pk in reportParams) {
      params = reportParams[pk]
      if (['3', '4'].includes(params.lt) && !params.url && params.urlref) {
        params.url = params.urlref
      }

      // 页面信息
      pageKey = params.ak + params.url
      if (pageData[pageKey]) {
        pageInfo = pageData[pageKey]
      } else {
        pageInfo = await page.getPageAndCreate(params.ak, params.url, params.ttpj)
        if (!pageInfo || pageInfo.length === 0) {
          console.log('Not found this page by param:', JSON.stringify(params))
          continue
          // return {
          //   code: 300,
          //   msg: 'Not found this entry page'
          // }
        }
        pageData[pageKey] = pageInfo
      }

      // 会话日志
      sessionKey = params.ak + params.uuid + params.p
      if (!this.sessionLogInfo[sessionKey]) {
        sessionLogInfo = await sessionLog.getSessionLog(params)
        if (sessionLogInfo.code) {
          return sessionLogInfo
        }
        if (this.debug) {
          console.log('sessionLogInfo', JSON.stringify(sessionLogInfo))
        }
        this.sessionLogInfo[sessionKey] = sessionLogInfo
      } else {
        sessionLogInfo = this.sessionLogInfo[sessionKey]
      }

      // 会话数据
      sessionLogKey = sessionLogInfo.data.sessionLogId.toString()
      if (!sessionData[sessionLogKey]) {
        sessionData[sessionLogKey] = {
          pageCount: sessionLogInfo.data.pageCount + 1,
          createTime: sessionLogInfo.data.createTime,
          pageId: pageInfo._id
        }
        console.log('add sessionData - ' + sessionLogKey, sessionData)
      } else {
        sessionData[sessionLogKey].pageCount += 1
        sessionData[sessionLogKey].pageId = pageInfo._id
        console.log('update sessionData - ' + sessionLogKey, sessionData)
      }

      // 上级页面信息
      pageKey = params.ak + params.urlref
      if (pageData[pageKey]) {
        referPageInfo = pageData[pageKey]
      } else {
        referPageInfo = await page.getPageAndCreate(params.ak, params.urlref, params.ttpj)
        if (!referPageInfo || referPageInfo.length === 0) {
          referPageInfo._id = ''
        }
        pageData[pageKey] = referPageInfo
      }

      // 填充数据
      fillParams.push({
        appid: params.ak,
        version: params.v ?? '',
        platform: platform.getPlatformCode(params.ut, params.p),
        channel: channel.getChannelCode(params),
        uuid: params.uuid,
        session_id: sessionLogInfo.data.sessionLogId,
        current_page_id: pageInfo._id,
        previous_page_id: referPageInfo._id,
        previous_page_duration: params.urlref_ts ? parseInt(params.urlref_ts) : 0,
        previous_page_is_entry: referPageInfo._id === sessionLogInfo.data.entryPageId ? 1 : 0,
        create_time: dateTime.getTime()
      })
    }

    if (fillParams.length === 0) {
      console.log('No page params')
      return {
        code: 200,
        msg: 'Invild param'
      }
    }

    const res = await this.insert(this.tableName, fillParams)
    if (res && res.inserted) {
      // 更新会话数据
      const nowTime = dateTime.getTime()
      for (const sid in sessionData) {
        const accessTime = nowTime - sessionData[sid].createTime
        const accessSenconds = accessTime > 1000 ? parseInt(accessTime / 1000) : 1
        if (this.debug) {
          console.log('update session log by sid-' + sid, {
            last_visit_time: nowTime,
            duration: accessSenconds,
            exit_page_id: sessionData[sid].pageId,
            page_count: sessionData[sid].pageCount
          })
        }
        await this.update(sessionLog.tableName, {
          last_visit_time: nowTime,
          duration: accessSenconds,
          exit_page_id: sessionData[sid].pageId,
          page_count: sessionData[sid].pageCount
        }, {
          _id: sid
        })
      }

      return {
        code: 0,
        msg: 'success'
      }
    } else {
      return {
        code: 500,
        msg: 'Filled error'
      }
    }
  }

  // 清理数据
  async clean (days) {
    days = Math.max(parseInt(days), 1)
    console.log('clean page logs - day:', days)

    const dateTime = new DateTime()

    const res = await this.delete(this.tableName, {
      create_time: {
        $lt: dateTime.getTimeBySetDays(0 - days)
      }
    })

    if (!res.code) {
      console.log('clean page log:', res)
    }
    return res
  }
}

// 事件日志
export class EventLog extends BaseMod {
  constructor () {
    super()
    this.tableName = 'stat-event-logs'
    this.sessionLogInfo = []
  }

  // 日志填充
  async fill (reportParams) {
    let params; let sessionKey, sessionLogKey; let sessionLogInfo; const sessionData = []
    const fillParams = []
    const shareParams = []
    const sessionLog = new SessionLog()
    const event = new StatEvent()
    const platform = new Platform()
    const dateTime = new DateTime()
    const channel = new Channel()
    for (const rk in reportParams) {
      params = reportParams[rk]

      sessionKey = params.ak + params.uuid + params.p
      if (!this.sessionLogInfo[sessionKey]) {
        // 会话日志
        sessionLogInfo = await sessionLog.getSessionLog(params)
        if (sessionLogInfo.code) {
          return sessionLogInfo
        }
        if (this.debug) {
          console.log('sessionLogInfo', JSON.stringify(sessionLogInfo))
        }
        this.sessionLogInfo[sessionKey] = sessionLogInfo
      } else {
        sessionLogInfo = this.sessionLogInfo[sessionKey]
      }

      // 会话数据
      sessionLogKey = sessionLogInfo.data.sessionLogId.toString()
      if (!sessionData[sessionLogKey]) {
        sessionData[sessionLogKey] = {
          eventCount: sessionLogInfo.data.eventCount + 1,
          createTime: sessionLogInfo.data.createTime
        }
      } else {
        sessionData[sessionLogKey].eventCount++
      }

      // 事件
      const eventInfo = await event.getEventAndCreate(params.ak, params.e_n)

      // 填充数据
      fillParams.push({
        appid: params.ak,
        version: params.v ?? '',
        platform: platform.getPlatformCode(params.ut, params.p),
        channel: channel.getChannelCode(params),
        uuid: params.uuid,
        session_id: sessionLogInfo.data.sessionLogId,
        page_id: sessionLogInfo.data.pageId,
        event_key: eventInfo.event_key,
        param: params.e_v ?? '',
        create_time: dateTime.getTime()
      })
      // 分享数据
      if (eventInfo.event_key === 'share') {
        shareParams.push(params)
      }
    }

    if (fillParams.length === 0) {
      return {
        code: 200,
        msg: 'Invild param'
      }
    }

    if (shareParams.length > 0) {
      const shareLog = new ShareLog()
      await shareLog.fill(shareParams, this.sessionLogInfo)
    }

    const res = await this.insert(this.tableName, fillParams)
    if (res && res.inserted) {
      const nowTime = dateTime.getTime()
      for (const sid in sessionData) {
        const accessTime = nowTime - sessionData[sid].createTime
        const accessSenconds = accessTime > 1000 ? parseInt(accessTime / 1000) : 1
        await this.update(sessionLog.tableName, {
          last_visit_time: nowTime,
          duration: accessSenconds,
          event_count: sessionData[sid].eventCount
        }, {
          _id: sid
        })
      }
      return {
        code: 0,
        msg: 'success'
      }
    } else {
      return {
        code: 500,
        msg: 'Filled error'
      }
    }
  }

  // 清理数据
  async clean (days) {
    days = Math.max(parseInt(days), 1)
    console.log('clean event logs - day:', days)

    const dateTime = new DateTime()

    const res = await this.delete(this.tableName, {
      create_time: {
        $lt: dateTime.getTimeBySetDays(0 - days)
      }
    })

    if (!res.code) {
      console.log('clean event log:', res)
    }
    return res
  }
}

// 错误日志
export class ErrorLog extends BaseMod {
  constructor () {
    super()
    this.tableName = 'stat-error-logs'
  }

  // 日志填充
  async fill (reportParams) {
    let params, errorHash, errorCount, cacheKey; const fillParams = []
    const platform = new Platform()
    const dateTime = new DateTime()
    const uniCrypto = new UniCrypto()
    const channel = new Channel()
    const {
      needCheck,
      checkTime
    } = this.getConfig('errorCheck')
    const errorCheckTime = Math.max(checkTime, 1)
    for (const rk in reportParams) {
      params = reportParams[rk]
      errorHash = uniCrypto.md5(params.em)
      cacheKey = 'error-count-' + errorHash
      // 校验在指定时间段内是否已存在相同的错误项
      if (needCheck) {
        errorCount = await this.getCache(cacheKey)
        if (!errorCount) {
          errorCount = await this.getCollection(this.tableName).where({
            error_hash: errorHash,
            create_time: {
              $gte: dateTime.getTime() - errorCheckTime * 60000
            }
          }).count()
          if (errorCount && errorCount.total > 0) {
            await this.setCache(cacheKey, errorCount, errorCheckTime * 60)
          }
        }

        if (errorCount && errorCount.total > 0) {
          if (this.debug) {
            console.log('This error have already existsed: ' + params.em)
          }
          continue
        }
      }

      // 填充数据
      fillParams.push({
        appid: params.ak,
        version: params.v ?? '',
        platform: platform.getPlatformCode(params.ut, params.p),
        channel: channel.getChannelCode(params),
        uuid: params.uuid,
        sdk_version: params.mpsdk ?? '',
        platform_version: params.mpv ?? '',
        error_msg: params.em ?? '',
        error_hash: errorHash,
        create_time: dateTime.getTime()
      })
    }

    if (fillParams.length === 0) {
      return {
        code: 200,
        msg: 'Invild param'
      }
    }

    const res = await this.insert(this.tableName, fillParams)
    if (res && res.inserted) {
      return {
        code: 0,
        msg: 'success'
      }
    } else {
      return {
        code: 500,
        msg: 'Filled error'
      }
    }
  }

  // 清理数据
  async clean (days) {
    days = Math.max(parseInt(days), 1)
    console.log('clean error logs - day:', days)

    const dateTime = new DateTime()

    const res = await this.delete(this.tableName, {
      create_time: {
        $lt: dateTime.getTimeBySetDays(0 - days)
      }
    })

    if (!res.code) {
      console.log('clean error log:', res)
    }
    return res
  }
}

// 分享日志
export class ShareLog extends BaseMod {
  constructor () {
    super()
    this.tableName = 'stat-share-logs'
  }

  // 日志填充
  async fill (reportParams, sessionLogData) {
    let params, sessionLogInfo, sessionKey; const fillParams = []
    const sessionLog = new SessionLog()
    const platform = new Platform()
    const dateTime = new DateTime()
    const channel = new Channel()
    for (const rk in reportParams) {
      params = reportParams[rk]

      sessionKey = params.ak + params.uuid + params.p
      if (!sessionLogData[sessionKey]) {
        // 会话日志
        sessionLogInfo = await sessionLog.getSessionLog(params)
        if (sessionLogInfo.code) {
          return sessionLogInfo
        }
        if (this.debug) {
          console.log('sessionLogInfo', JSON.stringify(sessionLogInfo))
        }
        sessionLogData[sessionKey] = sessionLogInfo
      } else {
        sessionLogInfo = sessionLogData[sessionKey]
      }

      // 填充数据
      fillParams.push({
        appid: params.ak,
        version: params.v ?? '',
        platform: platform.getPlatformCode(params.ut, params.p),
        channel: channel.getChannelCode(params),
        uuid: params.uuid,
        session_id: sessionLogInfo.data.sessionLogId,
        page_id: sessionLogInfo.data.pageId,
        create_time: dateTime.getTime()
      })
    }

    if (fillParams.length === 0) {
      return {
        code: 200,
        msg: 'Invild param'
      }
    }

    const res = await this.insert(this.tableName, fillParams)
    if (res && res.inserted) {
      return {
        code: 0,
        msg: 'success'
      }
    } else {
      return {
        code: 500,
        msg: 'Filled error'
      }
    }
  }

  // 清理数据
  async clean (days) {
    days = Math.max(parseInt(days), 1)
    console.log('clean share logs - day:', days)

    const dateTime = new DateTime()

    const res = await this.delete(this.tableName, {
      create_time: {
        $lt: dateTime.getTimeBySetDays(0 - days)
      }
    })

    if (!res.code) {
      console.log('clean share log:', res)
    }
    return res
  }
}

// 日志结果统计
export class StatResult extends BaseMod {
  constructor () {
    super()
    this.tableName = 'stat-result'
    this.platforms = []
    this.channels = []
    this.versions = []
  }

  // 数据统计
  async stat (type, date, reset) {
    const allowedType = ['hour', 'day', 'week', 'month']
    if (!allowedType.includes(type)) {
      return {
        code: 1002,
        msg: 'This type is not allowed'
      }
    }

    if (this.debug) {
      console.log('stat-result --type:' + type + ', date:' + date + ', reset:' + reset)
    }

    this.fillType = type
    const dateTime = new DateTime()
    const dateDimension = dateTime.getTimeDimensionByType(type, -1, date)
    this.startTime = dateDimension.startTime
    this.endTime = dateDimension.endTime
    if (this.debug) {
      console.log('dimension time', this.startTime + '--' + this.endTime)
    }

    // 查看当前时间段日志是否已存在,防止重复生成
    if (!reset) {
      const checkRes = await this.getCollection(this.tableName).where({
        dimension: this.fillType,
        start_time: this.startTime,
        end_time: this.endTime
      }).get()
      if (checkRes.data.length > 0) {
        console.log('log have existed')
        return {
          code: 1003,
          msg: 'This log have existed'
        }
      }
    } else {
      const delRes = await this.delete(this.tableName, {
        start_time: this.startTime,
        end_time: this.endTime
      })
      console.log('delete old data result:', JSON.stringify(delRes))
    }

    // 周月数据单独统计
    if (['week', 'month'].includes(this.fillType)) {
      return await this.statWeekOrMonth()
    }

    // 数据获取
    this.sessionLog = new SessionLog()
    const statRes = await this.aggregate(this.sessionLog.tableName, {
      project: {
        appid: 1,
        version: 1,
        platform: 1,
        channel: 1,
        is_first_visit: 1,
        page_count: 1,
        duration: 1,
        create_time: 1
      },
      match: {
        create_time: {
          $gte: this.startTime,
          $lte: this.endTime
        }
      },
      group: {
        _id: {
          appid: '$appid',
          version: '$version',
          platform: '$platform',
          channel: '$channel'
        },
        new_user_count: {
          $sum: '$is_first_visit'
        },
        page_view_count: {
          $sum: '$page_count'
        },
        total_duration: {
          $sum: '$duration'
        },
        session_times: {
          $sum: 1
        }
      },
      sort: {
        new_user_count: 1,
        page_view_count: 1,
        session_times: 1
      },
      getAll: true
    })

    let res = {
      code: 0,
      msg: 'success'
    }
    if (this.debug) {
      console.log('statRes', JSON.stringify(statRes))
    }
    if (statRes.data.length > 0) {
      this.fillData = []
      this.composes = []
      for (const i in statRes.data) {
        await this.fill(statRes.data[i])
      }
      await this.replenishStat()
      if (this.fillData.length > 0) {
        res = await this.batchInsert(this.tableName, this.fillData)
      }
    }
    return res
  }

  // 按周/月统计
  async statWeekOrMonth () {
    const statRes = await this.aggregate(this.tableName, {
      project: {
        appid: 1,
        version_id: 1,
        platform_id: 1,
        channel_id: 1,
        new_user_count: 1,
        page_visit_count: 1,
        app_launch_count: 1,
        error_count: 1,
        bounce_times: 1,
        duration: 1,
        dimension: 1,
        start_time: 1
      },
      match: {
        dimension: 'day',
        start_time: {
          $gte: this.startTime,
          $lte: this.endTime
        }
      },
      group: {
        _id: {
          appid: '$appid',
          version_id: '$version_id',
          platform_id: '$platform_id',
          channel_id: '$channel_id'
        },
        new_user_count: {
          $sum: '$new_user_count'
        },
        error_count: {
          $sum: '$error_count'
        },
        page_count: {
          $sum: '$page_visit_count'
        },
        total_duration: {
          $sum: '$duration'
        },
        session_times: {
          $sum: '$app_launch_count'
        },
        total_bounce_times: {
          $sum: '$bounce_times'
        }
      },
      sort: {
        new_user_count: 1,
        error_count: 1,
        page_count: 1
      },
      getAll: true
    })

    let res = {
      code: 0,
      msg: 'success'
    }
    if (this.debug) {
      console.log('statRes', JSON.stringify(statRes))
    }
    if (statRes.data.length > 0) {
      this.activeUsers = new ActvieUsers()
      this.fillData = []
      this.composes = []
      for (const i in statRes.data) {
        await this.getWeekOrMonthData(statRes.data[i])
      }
      await this.replenishStat()
      if (this.fillData.length > 0) {
        res = await this.batchInsert(this.tableName, this.fillData)
      }
    }
    return res
  }

  async getWeekOrMonthData (data) {
    // 查询活跃用户数
    const statVisitUserRes = await this.getCollection(this.activeUsers.tableName).where({
      ...data._id,
      dimension: this.fillType,
      create_time: {
        $gte: this.startTime,
        $lte: this.endTime
      }
    }).count()

    let activeUserCount = 0
    if (statVisitUserRes && statVisitUserRes.total > 0) {
      activeUserCount = statVisitUserRes.total
    }

    // 次均停留时长
    let avgSessionTime = 0
    if (data.total_duration > 0 && data.session_times > 0) {
      avgSessionTime = Math.round(data.total_duration / data.session_times)
    }

    // 人均停留时长
    let avgUserTime = 0
    if (data.total_duration > 0 && activeUserCount > 0) {
      avgUserTime = Math.round(data.total_duration / activeUserCount)
    }

    // 跳出率
    let bounceRate = 0
    if (data.total_bounce_times > 0 && data.session_times > 0) {
      bounceRate = data.total_bounce_times * 100 / data.session_times
      bounceRate = parseFloat(bounceRate.toFixed(2))
    }

    // 累计用户数
    let totalUsers = data.new_user_count
    const totalUserRes = await this.getCollection(this.tableName).where({
      appid: data._id.appid,
      platform_id: data._id.platform_id,
      channel_id: data._id.channel_id,
      version_id: data._id.version_id,
      dimension: this.fillType,
      start_time: {
        $lt: this.startTime
      }
    }).orderBy('start_time', 'desc').limit(1).get()
    if (totalUserRes && totalUserRes.data.length > 0) {
      totalUsers += totalUserRes.data[0].total_users
    }

    const insertParam = {
      appid: data._id.appid,
      platform_id: data._id.platform_id,
      channel_id: data._id.channel_id,
      version_id: data._id.version_id,
      total_users: totalUsers,
      new_user_count: data.new_user_count,
      active_user_count: activeUserCount,
      app_launch_count: data.session_times,
      page_visit_count: data.page_view_count,
      error_count: data.error_count,
      duration: data.total_duration,
      avg_session_time: avgSessionTime,
      avg_user_time: avgUserTime,
      bounce_times: data.total_bounce_times,
      bounce_rate: bounceRate,
      retention: {},
      dimension: this.fillType,
      stat_date: new DateTime().getDate('Ymd', this.startTime),
      start_time: this.startTime,
      end_time: this.endTime
    }

    this.fillData.push(insertParam)
    this.composes.push(data._id.appid + '_' + data._id.platform_id + '_' + data._id.channel_id + '_' + data._id.version_id)

    return insertParam
  }

  // 基础数据填充
  async fill (data) {
    // 平台信息
    let platformInfo = null
    if (this.platforms && this.platforms[data._id.platform]) {
      platformInfo = this.platforms[data._id.platform]
    } else {
      const platform = new Platform()
      platformInfo = await platform.getPlatformAndCreate(data._id.platform, null)
      if (!platformInfo || platformInfo.length === 0) {
        platformInfo._id = ''
      }
      this.platforms[data._id.platform] = platformInfo
      if (this.debug) {
        console.log('platformInfo', JSON.stringify(platformInfo))
      }
    }

    // 渠道信息
    let channelInfo = null
    const channelKey = data._id.appid + '_' + platformInfo._id + '_' + data._id.channel
    if (this.channels && this.channels[channelKey]) {
      channelInfo = this.channels[channelKey]
    } else {
      const channel = new Channel()
      channelInfo = await channel.getChannelAndCreate(data._id.appid, platformInfo._id, data._id.channel)
      if (!channelInfo || channelInfo.length === 0) {
        channelInfo._id = ''
      }
      this.channels[channelKey] = channelInfo
      if (this.debug) {
        console.log('channelInfo', JSON.stringify(channelInfo))
      }
    }

    // 版本信息
    let versionInfo = null
    const versionKey = data._id.appid + '_' + platformInfo._id + '_' + data._id.version
    if (this.versions && this.versions[versionKey]) {
      versionInfo = this.versions[versionKey]
    } else {
      const version = new Version()
      versionInfo = await version.getVersionAndCreate(data._id.appid, platformInfo._id, data._id.version)
      if (!versionInfo || versionInfo.length === 0) {
        versionInfo._id = ''
      }
      this.versions[versionKey] = versionInfo
      if (this.debug) {
        console.log('versionInfo', JSON.stringify(versionInfo))
      }
    }

    // 访问用户数统计
    const matchCondition = data._id
    Object.assign(matchCondition, {
      create_time: {
        $gte: this.startTime,
        $lte: this.endTime
      }
    })

    const statVisitUserRes = await this.aggregate(this.sessionLog.tableName, {
      project: {
        appid: 1,
        version: 1,
        platform: 1,
        channel: 1,
        uuid: 1,
        create_time: 1
      },
      match: matchCondition,
      group: [{
        _id: {
          uuid: '$uuid'
        }
      }, {
        _id: {},
        total_users: {
          $sum: 1
        }
      }]
    })

    let activeUserCount = 0
    if (statVisitUserRes.data.length > 0) {
      activeUserCount = statVisitUserRes.data[0].total_users
    }

    // 错误数量统计
    const errorLog = new ErrorLog()
    const statErrorRes = await this.getCollection(errorLog.tableName).where(matchCondition).count()
    let errorCount = 0
    if (statErrorRes && statErrorRes.total > 0) {
      errorCount = statErrorRes.total
    }

    // 次均停留时长
    let avgSessionTime = 0
    if (data.total_duration > 0 && data.session_times > 0) {
      avgSessionTime = Math.round(data.total_duration / data.session_times)
    }

    // 人均停留时长
    let avgUserTime = 0
    if (data.total_duration > 0 && activeUserCount > 0) {
      avgUserTime = Math.round(data.total_duration / activeUserCount)
    }

    // 跳出率
    let bounceTimes = 0
    const bounceRes = await this.getCollection(this.sessionLog.tableName).where({
      ...matchCondition,
      page_count: 1
    }).count()
    if (bounceRes && bounceRes.total > 0) {
      bounceTimes = bounceRes.total
    }
    let bounceRate = 0
    if (bounceTimes > 0 && data.session_times > 0) {
      bounceRate = bounceTimes * 100 / data.session_times
      bounceRate = parseFloat(bounceRate.toFixed(2))
    }

    // 应用启动次数 = 会话次数
    const launchCount = data.session_times
    // const pageLog = new PageLog()
    // const openCondition = matchCondition
    // Object.assign(openCondition, {
    //   previous_page_id: ''
    // })
    // const pageLogRes = await this.getCollection(pageLog.tableName).where(openCondition).count()
    // if (pageLogRes && pageLogRes.total > 0) {
    //   launchCount = pageLogRes.total
    // }

    // 累计用户数
    let totalUsers = data.new_user_count
    const totalUserRes = await this.getCollection(this.tableName).where({
      appid: data._id.appid,
      platform_id: platformInfo._id,
      channel_id: channelInfo._id,
      version_id: versionInfo._id,
      dimension: this.fillType,
      start_time: {
        $lt: this.startTime
      }
    }).orderBy('start_time', 'desc').limit(1).get()

    if (totalUserRes && totalUserRes.data.length > 0) {
      totalUsers += totalUserRes.data[0].total_users
    }

    const datetime = new DateTime()
    const insertParam = {
      appid: data._id.appid,
      platform_id: platformInfo._id,
      channel_id: channelInfo._id,
      version_id: versionInfo._id,
      total_users: totalUsers,
      new_user_count: data.new_user_count,
      active_user_count: activeUserCount,
      app_launch_count: launchCount,
      page_visit_count: data.page_view_count,
      error_count: errorCount,
      duration: data.total_duration,
      avg_session_time: avgSessionTime,
      avg_user_time: avgUserTime,
      bounce_times: bounceTimes,
      bounce_rate: bounceRate,
      retention: {},
      dimension: this.fillType,
      stat_date: datetime.getDate('Ymd', this.startTime),
      start_time: this.startTime,
      end_time: this.endTime
    }

    this.fillData.push(insertParam)
    this.composes.push(data._id.appid + '_' + platformInfo._id + '_' + channelInfo._id + '_' + versionInfo._id)

    return insertParam
  }

  // 基础统计数据补充
  async replenishStat () {
    if (this.debug) {
      console.log('composes data', this.composes)
    }

    const datetime = new DateTime()
    const {
      startTime
    } = datetime.getTimeDimensionByType(this.fillType, -1, this.startTime)

    // 上一阶段数据
    const preStatData = await this.selectAll(this.tableName, {
      start_time: startTime,
      dimension: this.fillType
    }, {
      appid: 1,
      platform_id: 1,
      channel_id: 1,
      version_id: 1,
      total_users: 1
    })

    if (!preStatData || preStatData.data.length === 0) {
      return false
    }

    if (this.debug) {
      console.log('preStatData', JSON.stringify(preStatData))
    }

    let preKey
    const preKeyArr = []
    for (const pi in preStatData.data) {
      preKey = preStatData.data[pi].appid + '_' + preStatData.data[pi].platform_id + '_' + preStatData.data[pi].channel_id + '_' + preStatData.data[pi].version_id
      if (!this.composes.includes(preKey) && !preKeyArr.includes(preKey)) {
        preKeyArr.push(preKey)
        if (this.debug) {
          console.log('preKey -add', preKey)
        }
        this.fillData.push({
          appid: preStatData.data[pi].appid,
          platform_id: preStatData.data[pi].platform_id,
          channel_id: preStatData.data[pi].channel_id,
          version_id: preStatData.data[pi].version_id,
          total_users: preStatData.data[pi].total_users,
          new_user_count: 0,
          active_user_count: 0,
          app_launch_count: 0,
          page_visit_count: 0,
          error_count: 0,
          duration: 0,
          avg_session_time: 0,
          avg_user_time: 0,
          bounce_times: 0,
          bounce_rate: 0,
          retention: {},
          dimension: this.fillType,
          stat_date: datetime.getDate('Ymd', this.startTime),
          start_time: this.startTime,
          end_time: this.endTime
        })
      } else if (this.debug) {
        console.log('preKey -have', preKey)
      }
    }

    return true
  }

  // 留存数据统计
  async retentionStat (type, date) {
    date = date ?? new DateTime().getTimeBySetDays(-1, date)
    const allowedType = ['day', 'week', 'month']
    if (!allowedType.includes(type)) {
      return {
        code: 1002,
        msg: 'This type is not allowed'
      }
    }
    let days = []
    switch (type) {
      case 'week':
      case 'month':
        days = [1, 2, 3, 4, 5, 6, 7, 8, 9]
        break
      default:
        days = [1, 2, 3, 4, 5, 6, 7, 14, 30]
        break
    }
    let res = {
      code: 0,
      msg: 'success'
    }
    for (const day in days) {
      if (type === 'day') {
        res = await this.retentionFillDayly(type, days[day], date)
      } else {
        res = await this.retentionFillWeekOrMonth(type, days[day], date)
      }
    }
    return res
  }

  async retentionFillDayly (type, day, date) {
    if (type !== 'day') {
      return {
        code: 301,
        msg: 'Type error:' + type
      }
    }
    const dateTime = new DateTime()
    const {
      startTime,
      endTime
    } = dateTime.getTimeDimensionByType(type, 0 - day, date)

    if (!startTime || !endTime) {
      return {
        code: 1001,
        msg: 'The statistic time get failed'
      }
    }

    // 截止时间范围
    const lastTimeInfo = dateTime.getTimeDimensionByType(type, 0, date)

    // 获取当时批次的统计日志
    const resultLogRes = await this.selectAll(this.tableName, {
      dimension: type,
      start_time: startTime,
      end_time: endTime
    })
    // const resultLogRes = await this.getCollection(this.tableName).where({
    //   dimension: type,
    //   start_time: startTime,
    //   end_time: endTime
    // }).get()

    if (this.debug) {
      console.log('resultLogRes', JSON.stringify(resultLogRes))
    }

    if (!resultLogRes || resultLogRes.data.length === 0) {
      if (this.debug) {
        console.log('Not found this log --' + type + ':' + day + ', start:' + startTime + ',endTime:' + endTime)
      }
      return {
        code: 1000,
        msg: 'Not found this log'
      }
    }

    const sessionLog = new SessionLog()
    const platform = new Platform()
    const channel = new Channel()
    const version = new Version()
    let res = null
    for (const resultIndex in resultLogRes.data) {
      const resultLog = resultLogRes.data[resultIndex]
      // 平台信息
      let platformInfo = null
      if (this.platforms && this.platforms[resultLog.platform_id]) {
        platformInfo = this.platforms[resultLog.platform_id]
      } else {
        platformInfo = await this.getById(platform.tableName, resultLog.platform_id)
        if (!platformInfo || platformInfo.length === 0) {
          platformInfo.code = ''
        }
        this.platforms[resultLog.platform_id] = platformInfo
        if (this.debug) {
          console.log('platformInfo', JSON.stringify(platformInfo))
        }
      }
      // 渠道信息
      let channelInfo = null
      if (this.channels && this.channels[resultLog.channel_id]) {
        channelInfo = this.channels[resultLog.channel_id]
      } else {
        channelInfo = await this.getById(channel.tableName, resultLog.channel_id)
        if (!channelInfo || channelInfo.length === 0) {
          channelInfo.channel_code = ''
        }
        this.channels[resultLog.channel_id] = channelInfo
        if (this.debug) {
          console.log('channelInfo', JSON.stringify(channelInfo))
        }
      }
      // 版本信息
      let versionInfo = null
      if (this.versions && this.versions[resultLog.version_id]) {
        versionInfo = this.versions[resultLog.version_id]
      } else {
        versionInfo = await this.getById(version.tableName, resultLog.version_id)
        if (!versionInfo || versionInfo.length === 0) {
          versionInfo.version = ''
        }
        this.versions[resultLog.version_id] = versionInfo
        if (this.debug) {
          console.log('versionInfo', JSON.stringify(versionInfo))
        }
      }
      // 获取该批次的活跃用户数
      const activeUserRes = await this.aggregate(sessionLog.tableName, {
        project: {
          appid: 1,
          version: 1,
          platform: 1,
          channel: 1,
          uuid: 1,
          create_time: 1
        },
        match: {
          appid: resultLog.appid,
          version: versionInfo.version,
          platform: platformInfo.code,
          channel: channelInfo.channel_code,
          create_time: {
            $gte: startTime,
            $lte: endTime
          }
        },
        group: {
          _id: {
            uuid: '$uuid'
          },
          create_time: {
            $min: '$create_time'
          },
          sessionCount: {
            $sum: 1
          }
        },
        sort: {
          create_time: 1,
          sessionCount: 1
        },
        getAll: true
      })

      if (this.debug) {
        console.log('activeUserRes', JSON.stringify(activeUserRes))
      }
      let activeUserRate = 0
      let activeUsers = 0
      if (activeUserRes && activeUserRes.data.length > 0) {
        const thisDayActiveUsers = activeUserRes.data.length
        const thisDayActiveUuids = []
        for (const tau in activeUserRes.data) {
          thisDayActiveUuids.push(activeUserRes.data[tau]._id.uuid)
        }

        if (this.debug) {
          console.log('thisDayActiveUuids', JSON.stringify(thisDayActiveUuids))
        }

        // 留存活跃用户数
        const retentionActiveUserRes = await this.aggregate(sessionLog.tableName, {
          project: {
            appid: 1,
            version: 1,
            platform: 1,
            channel: 1,
            uuid: 1,
            create_time: 1
          },
          match: {
            appid: resultLog.appid,
            version: versionInfo.version,
            platform: platformInfo.code,
            channel: channelInfo.channel_code,
            uuid: {
              $in: thisDayActiveUuids
            },
            create_time: {
              $gte: lastTimeInfo.startTime,
              $lte: lastTimeInfo.endTime
            }
          },
          group: [{
            _id: {
              uuid: '$uuid'
            }
          }, {
            _id: {},
            total_users: { $sum: 1 }
          }]
        })

        if (this.debug) {
          console.log('retentionActiveUserRes', JSON.stringify(retentionActiveUserRes))
        }
        if (retentionActiveUserRes && retentionActiveUserRes.data.length > 0) {
          // 活跃用户留存数
          activeUsers = retentionActiveUserRes.data[0].total_users
          // 活跃用户留存率
          activeUserRate = parseFloat((activeUsers * 100 / thisDayActiveUsers).toFixed(2))
        }
      }

      // 现在新增用户数
      const newUserRes = await this.aggregate(sessionLog.tableName, {
        project: {
          appid: 1,
          version: 1,
          platform: 1,
          channel: 1,
          is_first_visit: 1,
          uuid: 1,
          create_time: 1
        },
        match: {
          appid: resultLog.appid,
          version: versionInfo.version,
          platform: platformInfo.code,
          channel: channelInfo.channel_code,
          is_first_visit: 1,
          create_time: {
            $gte: startTime,
            $lte: endTime
          }
        },
        group: {
          _id: {
            uuid: '$uuid'
          },
          create_time: {
            $min: '$create_time'
          },
          sessionCount: {
            $sum: 1
          }
        },
        sort: {
          create_time: 1,
          sessionCount: 1
        },
        getAll: true
      })

      let newUserRate = 0
      let newUsers = 0
      if (newUserRes && newUserRes.data.length > 0) {
        const thisDayNewUsers = newUserRes.data.length
        const thisDayNewUuids = []
        for (const tau in newUserRes.data) {
          thisDayNewUuids.push(newUserRes.data[tau]._id.uuid)
        }

        if (this.debug) {
          console.log('thisDayNewUuids', JSON.stringify(thisDayNewUuids))
        }

        // 现在的活跃用户数
        const retentionNewUserRes = await this.aggregate(sessionLog.tableName, {
          project: {
            appid: 1,
            version: 1,
            platform: 1,
            channel: 1,
            is_first_visit: 1,
            uuid: 1,
            create_time: 1
          },
          match: {
            appid: resultLog.appid,
            version: versionInfo.version,
            platform: platformInfo.code,
            channel: channelInfo.channel_code,
            is_first_visit: 1,
            uuid: {
              $in: thisDayNewUuids
            },
            create_time: {
              $gte: lastTimeInfo.startTime,
              $lte: lastTimeInfo.endTime
            }
          },
          group: [{
            _id: {
              uuid: '$uuid'
            }
          }, {
            _id: {},
            total_users: { $sum: 1 }
          }]
        })

        if (retentionNewUserRes && retentionNewUserRes.data.length > 0) {
          // 活跃用户留存数
          newUsers = retentionNewUserRes.data[0].total_users
          // 活跃用户留存率
          newUserRate = parseFloat((newUsers * 100 / thisDayNewUsers).toFixed(2))
        }
      }

      // 数据更新
      const retentionData = resultLog.retention
      const dataKey = type.substr(0, 1) + '_' + day
      if (!retentionData.active_user) {
        retentionData.active_user = {}
      }
      retentionData.active_user[dataKey] = {
        user_count: activeUsers,
        user_rate: activeUserRate
      }
      if (!retentionData.new_user) {
        retentionData.new_user = {}
      }
      retentionData.new_user[dataKey] = {
        user_count: newUsers,
        user_rate: newUserRate
      }

      if (this.debug) {
        console.log('retentionData', JSON.stringify(retentionData))
      }

      res = await this.update(this.tableName, {
        retention: retentionData
      }, {
        _id: resultLog._id
      })
    }

    if (res && res.id) {
      return {
        code: 0,
        msg: 'success'
      }
    } else {
      return {
        code: 500,
        msg: 'retention data update failed'
      }
    }
  }

  // 留存数据填充
  async retentionFillWeekOrMonth (type, day, date) {
    if (!['week', 'month'].includes(type)) {
      return {
        code: 301,
        msg: 'Type error:' + type
      }
    }
    const dateTime = new DateTime()
    const {
      startTime,
      endTime
    } = dateTime.getTimeDimensionByType(type, 0 - day, date)

    if (!startTime || !endTime) {
      return {
        code: 1001,
        msg: 'The statistic time get failed'
      }
    }

    // 截止时间范围
    const lastTimeInfo = dateTime.getTimeDimensionByType(type, 0, date)

    // 获取当时批次的统计日志
    const resultLogRes = await this.selectAll(this.tableName, {
      dimension: type,
      start_time: startTime,
      end_time: endTime
    })
    // const resultLogRes = await this.getCollection(this.tableName).where({
    //   dimension: type,
    //   start_time: startTime,
    //   end_time: endTime
    // }).get()

    if (this.debug) {
      console.log('resultLogRes', JSON.stringify(resultLogRes))
    }

    if (!resultLogRes || resultLogRes.data.length === 0) {
      if (this.debug) {
        console.log('Not found this session log --' + type + ':' + day + ', start:' + startTime + ',endTime:' + endTime)
      }
      return {
        code: 1000,
        msg: 'Not found this session log'
      }
    }

    const activeUsersObj = new ActvieUsers()
    let res = null; let activeUserRes; let activeUserRate; let activeUsers; let newUserRate; let newUsers
    for (const resultIndex in resultLogRes.data) {
      const resultLog = resultLogRes.data[resultIndex]
      // 获取该批次的活跃用户数
      activeUserRes = await this.selectAll(activeUsersObj.tableName, {
        appid: resultLog.appid,
        platform_id: resultLog.platform_id,
        channel_id: resultLog.channel_id,
        version_id: resultLog.version_id,
        dimension: type,
        create_time: {
          $gte: startTime,
          $lte: endTime
        }
      }, {
        uuid: 1
      })

      if (this.debug) {
        console.log('activeUserRes', JSON.stringify(activeUserRes))
      }

      activeUserRate = 0
      activeUsers = 0
      if (activeUserRes && activeUserRes.data.length > 0) {
        const thisDayActiveUsers = activeUserRes.data.length
        const thisDayActiveUuids = []
        for (const tau in activeUserRes.data) {
          thisDayActiveUuids.push(activeUserRes.data[tau].uuid)
        }
        if (this.debug) {
          console.log('thisDayActiveUuids', JSON.stringify(thisDayActiveUuids))
        }
        // 留存活跃用户数
        const retentionActiveUserRes = await this.getCollection(activeUsersObj.tableName).where({
          appid: resultLog.appid,
          platform_id: resultLog.platform_id,
          channel_id: resultLog.channel_id,
          version_id: resultLog.version_id,
          uuid: {
            $in: thisDayActiveUuids
          },
          dimension: type,
          create_time: {
            $gte: lastTimeInfo.startTime,
            $lte: lastTimeInfo.endTime
          }
        }).count()

        if (this.debug) {
          console.log('retentionActiveUserRes', JSON.stringify(retentionActiveUserRes))
        }
        if (retentionActiveUserRes && retentionActiveUserRes.total > 0) {
          // 活跃用户留存数
          activeUsers = retentionActiveUserRes.total
          // 活跃用户留存率
          activeUserRate = parseFloat((activeUsers * 100 / thisDayActiveUsers).toFixed(2))
        }
      }

      // 获取该批次的新增用户数
      const newUserRes = await this.selectAll(activeUsersObj.tableName, {
        appid: resultLog.appid,
        platform_id: resultLog.platform_id,
        channel_id: resultLog.channel_id,
        version_id: resultLog.version_id,
        is_new: 1,
        dimension: type,
        create_time: {
          $gte: startTime,
          $lte: endTime
        }
      }, {
        uuid: 1
      })

      newUserRate = 0
      newUsers = 0
      if (newUserRes && newUserRes.data.length > 0) {
        const thisDayNewUsers = newUserRes.data.length
        const thisDayNewUuids = []
        for (const tau in newUserRes.data) {
          thisDayNewUuids.push(newUserRes.data[tau].uuid)
        }

        // 新增用户留存数
        const retentionNewUserRes = await this.getCollection(activeUsersObj.tableName).where({
          appid: resultLog.appid,
          platform_id: resultLog.platform_id,
          channel_id: resultLog.channel_id,
          version_id: resultLog.version_id,
          uuid: {
            $in: thisDayNewUuids
          },
          dimension: type,
          create_time: {
            $gte: lastTimeInfo.startTime,
            $lte: lastTimeInfo.endTime
          }
        }).count()

        if (retentionNewUserRes && retentionNewUserRes.total > 0) {
          // 新增用户留存数
          newUsers = retentionNewUserRes.total
          // 新增用户留存率
          newUserRate = parseFloat((newUsers * 100 / thisDayNewUsers).toFixed(2))
        }
      }

      // 数据更新
      const retentionData = resultLog.retention
      const dataKey = type.substr(0, 1) + '_' + day
      if (!retentionData.active_user) {
        retentionData.active_user = {}
      }
      retentionData.active_user[dataKey] = {
        user_count: activeUsers,
        user_rate: activeUserRate
      }
      if (!retentionData.new_user) {
        retentionData.new_user = {}
      }
      retentionData.new_user[dataKey] = {
        user_count: newUsers,
        user_rate: newUserRate
      }

      if (this.debug) {
        console.log('retentionData', JSON.stringify(retentionData))
      }

      res = await this.update(this.tableName, {
        retention: retentionData
      }, {
        _id: resultLog._id
      })
    }

    if (res && res.id) {
      return {
        code: 0,
        msg: 'success'
      }
    } else {
      return {
        code: 500,
        msg: 'retention data update failed'
      }
    }
  }

  async clean () {
    // 清理实时统计的日志
    const days = 7

    console.log('clean hour logs - day:', days)

    const dateTime = new DateTime()

    const res = await this.delete(this.tableName, {
      dimension: 'hour',
      start_time: {
        $lt: dateTime.getTimeBySetDays(0 - days)
      }
    })

    if (!res.code) {
      console.log('clean hour logs - res:', res)
    }
  }
}

// 活跃用户表 - 由每日跑批合并，仅添加当日新用户，以及本周/本月首次访问的老用户。
export class ActvieUsers extends BaseMod {
  constructor () {
    super()
    this.tableName = 'stat-active-users'
    this.platforms = []
    this.channels = []
    this.versions = []
  }

  async stat (date, reset) {
    const dateTime = new DateTime()
    const dateDimension = dateTime.getTimeDimensionByType('day', -1, date)
    this.startTime = dateDimension.startTime
    // 查看当前时间段数据是否已存在,防止重复生成
    if (!reset) {
      const checkRes = await this.getCollection(this.tableName).where({
        create_time: {
          $gte: dateDimension.startTime,
          $lte: dateDimension.endTime
        }
      }).get()
      if (checkRes.data.length > 0) {
        console.log('data have exists')
        return {
          code: 1003,
          msg: 'Users data in this time have already existed'
        }
      }
    } else {
      const delRes = await this.delete(this.tableName, {
        create_time: {
          $gte: dateDimension.startTime,
          $lte: dateDimension.endTime
        }
      })
      console.log('delete old data result:', JSON.stringify(delRes))
    }

    const sessionLog = new SessionLog()
    const statRes = await this.aggregate(sessionLog.tableName, {
      project: {
        appid: 1,
        version: 1,
        platform: 1,
        channel: 1,
        is_first_visit: 1,
        create_time: 1,
        uuid: 1
      },
      match: {
        create_time: {
          $gte: dateDimension.startTime,
          $lte: dateDimension.endTime
        }
      },
      group: {
        _id: {
          appid: '$appid',
          version: '$version',
          platform: '$platform',
          channel: '$channel',
          uuid: '$uuid'
        },
        is_new: {
          $max: '$is_first_visit'
        },
        create_time: {
          $min: '$create_time'
        }
      },
      sort: {
        create_time: 1
      },
      getAll: true
    })

    let res = {
      code: 0,
      msg: 'success'
    }
    // if (this.debug) {
    //   console.log('statRes', JSON.stringify(statRes))
    // }
    if (statRes.data.length > 0) {
      const uniCrypto = new UniCrypto()
      // 同应用、平台、渠道、版本的数据合并
      const statData = []; let statKey; let data

      for (const sti in statRes.data) {
        data = statRes.data[sti]
        statKey = uniCrypto.md5(data._id.appid + data._id.platform + data._id.version + data._id.channel)
        if (!statData[statKey]) {
          statData[statKey] = {
            appid: data._id.appid,
            platform: data._id.platform,
            version: data._id.version,
            channel: data._id.channel,
            uuids: [],
            info: []
          }
          statData[statKey].uuids.push(data._id.uuid)
          statData[statKey].info[data._id.uuid] = {
            is_new: data.is_new,
            create_time: data.create_time
          }
        } else {
          statData[statKey].uuids.push(data._id.uuid)
          statData[statKey].info[data._id.uuid] = {
            is_new: data.is_new,
            create_time: data.create_time
          }
        }
      }

      this.fillData = []
      for (const sk in statData) {
        await this.getFillData(statData[sk])
      }

      if (this.fillData.length > 0) {
        res = await this.batchInsert(this.tableName, this.fillData)
      }
    }
    return res
  }

  async getFillData (data) {
    // 平台信息
    let platformInfo = null
    if (this.platforms && this.platforms[data.platform]) {
      platformInfo = this.platforms[data.platform]
    } else {
      const platform = new Platform()
      platformInfo = await platform.getPlatformAndCreate(data.platform, null)
      if (!platformInfo || platformInfo.length === 0) {
        platformInfo._id = ''
      }
      this.platforms[data.platform] = platformInfo
      if (this.debug) {
        console.log('platformInfo', JSON.stringify(platformInfo))
      }
    }

    // 渠道信息
    let channelInfo = null
    const channelKey = data.appid + '_' + platformInfo._id + '_' + data.channel
    if (this.channels && this.channels[channelKey]) {
      channelInfo = this.channels[channelKey]
    } else {
      const channel = new Channel()
      channelInfo = await channel.getChannelAndCreate(data.appid, platformInfo._id, data.channel)
      if (!channelInfo || channelInfo.length === 0) {
        channelInfo._id = ''
      }
      this.channels[channelKey] = channelInfo
      if (this.debug) {
        console.log('channelInfo', JSON.stringify(channelInfo))
      }
    }

    // 版本信息
    let versionInfo = null
    const versionKey = data.appid + '_' + platformInfo._id + '_' + data.version
    if (this.versions && this.versions[versionKey]) {
      versionInfo = this.versions[versionKey]
    } else {
      const version = new Version()
      versionInfo = await version.getVersionAndCreate(data.appid, platformInfo._id, data.version)
      if (!versionInfo || versionInfo.length === 0) {
        versionInfo._id = ''
      }
      this.versions[versionKey] = versionInfo
      if (this.debug) {
        console.log('versionInfo', JSON.stringify(versionInfo))
      }
    }

    // 是否在本周内已存在
    const datetime = new DateTime()
    const dateDimension = datetime.getTimeDimensionByType('week', 0, this.startTime)

    // 取出本周已经存储的uuid
    const weekHaveUserList = []
    const haveWeekList = await this.selectAll(this.tableName, {
      appid: data.appid,
      version_id: versionInfo._id,
      platform_id: platformInfo._id,
      channel_id: channelInfo._id,
      uuid: { $in: data.uuids },
      dimension: 'week',
      create_time: {
        $gte: dateDimension.startTime,
        $lte: dateDimension.endTime
      }
    }, {
      uuid: 1
    })

    if (this.debug) {
      console.log('haveWeekList', JSON.stringify(haveWeekList))
    }

    if (haveWeekList.data.length > 0) {
      for (const hui in haveWeekList.data) {
        weekHaveUserList.push(haveWeekList.data[hui].uuid)
      }
    }

    // 取出本月已经存储的uuid
    const dateMonthDimension = datetime.getTimeDimensionByType('month', 0, this.startTime)
    const monthHaveUserList = []
    const haveMonthList = await this.selectAll(this.tableName, {
      appid: data.appid,
      version_id: versionInfo._id,
      platform_id: platformInfo._id,
      channel_id: channelInfo._id,
      uuid: { $in: data.uuids },
      dimension: 'month',
      create_time: {
        $gte: dateMonthDimension.startTime,
        $lte: dateMonthDimension.endTime
      }
    }, {
      uuid: 1
    })

    if (this.debug) {
      console.log('haveMonthList', JSON.stringify(haveMonthList))
    }

    if (haveMonthList.data.length > 0) {
      for (const hui in haveMonthList.data) {
        monthHaveUserList.push(haveMonthList.data[hui].uuid)
      }
    }

    for (const ui in data.uuids) {
      if (!weekHaveUserList.includes(data.uuids[ui])) {
        this.fillData.push({
          appid: data.appid,
          platform_id: platformInfo._id,
          channel_id: channelInfo._id,
          version_id: versionInfo._id,
          is_new: data.info[data.uuids[ui]].is_new,
          uuid: data.uuids[ui],
          dimension: 'week',
          create_time: data.info[data.uuids[ui]].create_time
        })
      }

      if (!monthHaveUserList.includes(data.uuids[ui])) {
        this.fillData.push({
          appid: data.appid,
          platform_id: platformInfo._id,
          channel_id: channelInfo._id,
          version_id: versionInfo._id,
          is_new: data.info[data.uuids[ui]].is_new,
          uuid: data.uuids[ui],
          dimension: 'month',
          create_time: data.info[data.uuids[ui]].create_time
        })
      }
    }

    return true
  }

  async clean () {
    // 清除周数据
    const weeks = 2
    console.log('clean user\'s weekly logs - week:', weeks)

    const dateTime = new DateTime()

    const res = await this.delete(this.tableName, {
      dimension: 'week',
      create_time: {
        $lt: dateTime.getTimeBySetWeek(0 - weeks)
      }
    })

    if (!res.code) {
      console.log('clean user\'s weekly logs - res:', res)
    }

    // 清除月数据
    const monthes = 9
    console.log('clean user\'s monthly logs - month:', monthes)
    const monthRes = await this.delete(this.tableName, {
      dimension: 'month',
      create_time: {
        $lt: dateTime.getTimeBySetMonth(0 - monthes)
      }
    })
    if (!monthRes.code) {
      console.log('clean user\'s monthly logs - res:', res)
    }
    return monthRes
  }
}

// 页面结果统计
export class PageResult extends BaseMod {
  constructor () {
    super()
    this.tableName = 'stat-page-result'
    this.platforms = []
    this.channels = []
    this.versions = []
  }

  // 数据统计
  async stat (type, date, reset) {
    const allowedType = ['day']
    if (!allowedType.includes(type)) {
      return {
        code: 1002,
        msg: 'This type is not allowed'
      }
    }
    this.fillType = type
    const dateTime = new DateTime()
    const dateDimension = dateTime.getTimeDimensionByType(type, -1, date)
    this.startTime = dateDimension.startTime
    this.endTime = dateDimension.endTime
    if (this.debug) {
      console.log('dimension time', this.startTime + '--' + this.endTime)
    }

    // 查看当前时间段日志是否已存在,防止重复生成
    if (!reset) {
      const checkRes = await this.getCollection(this.tableName).where({
        start_time: this.startTime,
        end_time: this.endTime
      }).get()
      if (checkRes.data.length > 0) {
        console.log('log have exists')
        return {
          code: 1003,
          msg: 'This log have existed'
        }
      }
    } else {
      const delRes = await this.delete(this.tableName, {
        start_time: this.startTime,
        end_time: this.endTime
      })
      console.log('delete old data result:', JSON.stringify(delRes))
    }

    // 数据获取
    this.pageLog = new PageLog()
    const statRes = await this.aggregate(this.pageLog.tableName, {
      project: {
        appid: 1,
        version: 1,
        platform: 1,
        channel: 1,
        current_page_id: 1,
        create_time: 1
      },
      match: {
        create_time: {
          $gte: this.startTime,
          $lte: this.endTime
        }
      },
      group: {
        _id: {
          appid: '$appid',
          version: '$version',
          platform: '$platform',
          channel: '$channel',
          current_page_id: '$current_page_id'
        },
        visit_times: {
          $sum: 1
        }
      },
      sort: {
        visit_times: 1
      },
      getAll: true
    })

    let res = {
      code: 0,
      msg: 'success'
    }
    if (this.debug) {
      console.log('Page statRes', JSON.stringify(statRes))
    }
    if (statRes.data.length > 0) {
      this.fillData = []
      for (const i in statRes.data) {
        await this.fill(statRes.data[i])
      }

      if (this.fillData.length > 0) {
        res = await this.batchInsert(this.tableName, this.fillData)
      }
    }
    return res
  }

  async fill (data) {
    // 平台信息
    let platformInfo = null
    if (this.platforms && this.platforms[data._id.platform]) {
      platformInfo = this.platforms[data._id.platform]
    } else {
      const platform = new Platform()
      platformInfo = await platform.getPlatformAndCreate(data._id.platform, null)
      if (!platformInfo || platformInfo.length === 0) {
        platformInfo._id = ''
      }
      this.platforms[data._id.platform] = platformInfo
      if (this.debug) {
        console.log('platformInfo', JSON.stringify(platformInfo))
      }
    }

    // 渠道信息
    let channelInfo = null
    const channelKey = data._id.appid + '_' + platformInfo._id + '_' + data._id.channel
    if (this.channels && this.channels[channelKey]) {
      channelInfo = this.channels[channelKey]
    } else {
      const channel = new Channel()
      channelInfo = await channel.getChannelAndCreate(data._id.appid, platformInfo._id, data._id.channel)
      if (!channelInfo || channelInfo.length === 0) {
        channelInfo._id = ''
      }
      this.channels[channelKey] = channelInfo
      if (this.debug) {
        console.log('channelInfo', JSON.stringify(channelInfo))
      }
    }

    // 版本信息
    let versionInfo = null
    const versionKey = data._id.appid + '_' + platformInfo._id + '_' + data._id.version
    if (this.versions && this.versions[versionKey]) {
      versionInfo = this.versions[versionKey]
    } else {
      const version = new Version()
      versionInfo = await version.getVersionAndCreate(data._id.appid, platformInfo._id, data._id.version)
      if (!versionInfo || versionInfo.length === 0) {
        versionInfo._id = ''
      }
      this.versions[versionKey] = versionInfo
      if (this.debug) {
        console.log('versionInfo', JSON.stringify(versionInfo))
      }
    }

    // 当前页面访问人数
    const matchCondition = data._id
    Object.assign(matchCondition, {
      create_time: {
        $gte: this.startTime,
        $lte: this.endTime
      }
    })
    if (this.debug) {
      console.log('matchCondition', JSON.stringify(matchCondition))
    }

    const statPageUserRes = await this.aggregate(this.pageLog.tableName, {
      project: {
        appid: 1,
        version: 1,
        platform: 1,
        channel: 1,
        uuid: 1,
        current_page_id: 1,
        create_time: 1
      },
      match: matchCondition,
      group: [{
        _id: {
          uuid: '$uuid'
        }
      }, {
        _id: {},
        total_users: {
          $sum: 1
        }
      }]
    })

    let pageVisitUsers = 0
    if (statPageUserRes.data.length > 0) {
      pageVisitUsers = statPageUserRes.data[0].total_users
    }

    // 退出次数
    const sessionLog = new SessionLog()
    let existTimes = 0
    const existRes = await this.getCollection(sessionLog.tableName).where({
      appid: data._id.appid,
      version: data._id.version,
      platform: data._id.platform,
      channel: data._id.channel,
      exit_page_id: data._id.current_page_id,
      create_time: {
        $gte: this.startTime,
        $lte: this.endTime
      }
    }).count()
    if (existRes && existRes.total > 0) {
      existTimes = existRes.total
    }

    // 访问时长
    const statPageDurationRes = await this.aggregate(this.pageLog.tableName, {
      project: {
        appid: 1,
        version: 1,
        platform: 1,
        channel: 1,
        previous_page_id: 1,
        previous_page_duration: 1,
        create_time: 1
      },
      match: {
        appid: data._id.appid,
        version: data._id.version,
        platform: data._id.platform,
        channel: data._id.channel,
        previous_page_id: data._id.current_page_id,
        create_time: {
          $gte: this.startTime,
          $lte: this.endTime
        }
      },
      group: {
        _id: {},
        total_duration: {
          $sum: '$previous_page_duration'
        }
      }
    })

    let totalDuration = 0
    if (statPageDurationRes.data.length > 0) {
      totalDuration = statPageDurationRes.data[0].total_duration
    }

    // 分享次数
    const shareLog = new ShareLog()
    const statShareRes = await this.aggregate(shareLog.tableName, {
      project: {
        appid: 1,
        version: 1,
        platform: 1,
        channel: 1,
        page_id: 1,
        create_time: 1
      },
      match: {
        appid: data._id.appid,
        version: data._id.version,
        platform: data._id.platform,
        channel: data._id.channel,
        page_id: data._id.current_page_id,
        create_time: {
          $gte: this.startTime,
          $lte: this.endTime
        }
      },
      group: {
        _id: {},
        share_count: {
          $sum: 1
        }
      }
    })

    let shareCount = 0
    if (statShareRes.data.length > 0) {
      shareCount = statShareRes.data[0].share_count
    }

    // 作为入口页的总次数和总访问时长
    const statPageEntryCountRes = await this.aggregate(this.pageLog.tableName, {
      project: {
        appid: 1,
        version: 1,
        platform: 1,
        channel: 1,
        previous_page_id: 1,
        previous_page_duration: 1,
        previous_page_is_entry: 1,
        create_time: 1
      },
      match: {
        appid: data._id.appid,
        version: data._id.version,
        platform: data._id.platform,
        channel: data._id.channel,
        previous_page_id: data._id.current_page_id,
        previous_page_is_entry: 1,
        create_time: {
          $gte: this.startTime,
          $lte: this.endTime
        }
      },
      group: {
        _id: {},
        entry_count: {
          $sum: 1
        },
        entry_duration: {
          $sum: '$previous_page_duration'
        }
      }
    })

    let entryCount = 0
    let entryDuration = 0
    if (statPageEntryCountRes.data.length > 0) {
      entryCount = statPageEntryCountRes.data[0].entry_count
      entryDuration = statPageEntryCountRes.data[0].entry_duration
    }

    // 作为入口页的总人数
    const statPageEntryUsersRes = await this.aggregate(this.pageLog.tableName, {
      project: {
        appid: 1,
        version: 1,
        platform: 1,
        channel: 1,
        uuid: 1,
        previous_page_id: 1,
        previous_page_is_entry: 1,
        create_time: 1
      },
      match: {
        appid: data._id.appid,
        version: data._id.version,
        platform: data._id.platform,
        channel: data._id.channel,
        previous_page_id: data._id.current_page_id,
        previous_page_is_entry: 1,
        create_time: {
          $gte: this.startTime,
          $lte: this.endTime
        }
      },
      group: [{
        _id: {
          uuid: '$uuid'
        }
      }, {
        _id: {},
        entry_users: {
          $sum: 1
        }
      }]
    })

    let entryUsers = 0
    if (statPageEntryUsersRes.data.length > 0) {
      entryUsers = statPageEntryUsersRes.data[0].entry_users
    }

    // 跳出率
    let bounceTimes = 0
    const bounceRes = await this.getCollection(sessionLog.tableName).where({
      appid: data._id.appid,
      version: data._id.version,
      platform: data._id.platform,
      channel: data._id.channel,
      entry_page_id: data._id.current_page_id,
      page_count: 1,
      create_time: {
        $gte: this.startTime,
        $lte: this.endTime
      }
    }).count()
    if (bounceRes && bounceRes.total > 0) {
      bounceTimes = bounceRes.total
    }
    let bounceRate = 0
    if (bounceTimes > 0 && data.visit_times > 0) {
      bounceRate = bounceTimes * 100 / data.visit_times
      bounceRate = parseFloat(bounceRate.toFixed(2))
    }

    // 数据填充
    const datetime = new DateTime()
    const insertParams = {
      appid: data._id.appid,
      platform_id: platformInfo._id,
      channel_id: channelInfo._id,
      version_id: versionInfo._id,
      page_id: data._id.current_page_id,
      visit_times: data.visit_times,
      visit_users: pageVisitUsers,
      exit_times: existTimes,
      duration: totalDuration,
      share_count: shareCount,
      entry_users: entryUsers,
      entry_count: entryCount,
      entry_duration: entryDuration,
      bounce_times: bounceTimes,
      bounce_rate: bounceRate,
      dimension: this.fillType,
      stat_date: datetime.getDate('Ymd', this.startTime),
      start_time: this.startTime,
      end_time: this.endTime
    }

    this.fillData.push(insertParams)
    return insertParams
    // const res = await this.insert(this.tableName, insertParams)
    // if (this.debug) {
    //   console.log('save res', JSON.stringify(res))
    // }
    // if (res && res.id) {
    //   return {
    //     code: 0,
    //     msg: 'have done'
    //   }
    // } else {
    //   console.log(this.tableName + ' log saved failed by params:', JSON.stringify(insertParams))
    //   return {
    //     code: 400,
    //     msg: 'log save error'
    //   }
    // }
  }
}

// 事件结果统计
export class EventResult extends BaseMod {
  constructor () {
    super()
    this.tableName = 'stat-event-result'
    this.platforms = []
    this.channels = []
    this.versions = []
  }

  // 数据统计
  async stat (type, date, reset) {
    const allowedType = ['day']
    if (!allowedType.includes(type)) {
      return {
        code: 1002,
        msg: 'This type is not allowed'
      }
    }
    this.fillType = type
    const dateTime = new DateTime()
    const dateDimension = dateTime.getTimeDimensionByType(type, -1, date)
    this.startTime = dateDimension.startTime
    this.endTime = dateDimension.endTime
    if (this.debug) {
      console.log('dimension time', this.startTime + '--' + this.endTime)
    }

    // 查看当前时间段日志是否已存在,防止重复生成
    if (!reset) {
      const checkRes = await this.getCollection(this.tableName).where({
        start_time: this.startTime,
        end_time: this.endTime
      }).get()
      if (checkRes.data.length > 0) {
        console.log('event log have existed')
        return {
          code: 1003,
          msg: 'This log have existed'
        }
      }
    } else {
      const delRes = await this.delete(this.tableName, {
        start_time: this.startTime,
        end_time: this.endTime
      })
      console.log('delete old data result:', JSON.stringify(delRes))
    }

    // 数据获取
    this.eventLog = new EventLog()
    const statRes = await this.aggregate(this.eventLog.tableName, {
      project: {
        appid: 1,
        version: 1,
        platform: 1,
        channel: 1,
        event_key: 1,
        uuid: 1,
        create_time: 1
      },
      match: {
        create_time: {
          $gte: this.startTime,
          $lte: this.endTime
        }
      },
      group: {
        _id: {
          appid: '$appid',
          version: '$version',
          platform: '$platform',
          channel: '$channel',
          event_key: '$event_key'
        },
        event_count: {
          $sum: 1
        }
      },
      sort: {
        event_count: 1
      },
      getAll: true
    })

    let res = {
      code: 0,
      msg: 'success'
    }
    if (this.debug) {
      console.log('statRes', JSON.stringify(statRes))
    }
    if (statRes.data.length > 0) {
      this.fillData = []
      for (const i in statRes.data) {
        await this.fill(statRes.data[i])
      }
      if (this.fillData.length > 0) {
        res = await this.batchInsert(this.tableName, this.fillData)
      }
    }
    return res
  }

  async fill (data) {
    // 平台信息
    let platformInfo = null
    if (this.platforms && this.platforms[data._id.platform]) {
      platformInfo = this.platforms[data._id.platform]
    } else {
      const platform = new Platform()
      platformInfo = await platform.getPlatformAndCreate(data._id.platform, null)
      if (!platformInfo || platformInfo.length === 0) {
        platformInfo._id = ''
      }
      this.platforms[data._id.platform] = platformInfo
      if (this.debug) {
        console.log('platformInfo', JSON.stringify(platformInfo))
      }
    }

    // 渠道信息
    let channelInfo = null
    const channelKey = data._id.appid + '_' + platformInfo._id + '_' + data._id.channel
    if (this.channels && this.channels[channelKey]) {
      channelInfo = this.channels[channelKey]
    } else {
      const channel = new Channel()
      channelInfo = await channel.getChannelAndCreate(data._id.appid, platformInfo._id, data._id.channel)
      if (!channelInfo || channelInfo.length === 0) {
        channelInfo._id = ''
      }
      this.channels[channelKey] = channelInfo
      if (this.debug) {
        console.log('channelInfo', JSON.stringify(channelInfo))
      }
    }

    // 版本信息
    let versionInfo = null
    const versionKey = data._id.appid + '_' + platformInfo._id + '_' + data._id.version
    if (this.versions && this.versions[versionKey]) {
      versionInfo = this.versions[versionKey]
    } else {
      const version = new Version()
      versionInfo = await version.getVersionAndCreate(data._id.appid, platformInfo._id, data._id.version)
      if (!versionInfo || versionInfo.length === 0) {
        versionInfo._id = ''
      }
      this.versions[versionKey] = versionInfo
      if (this.debug) {
        console.log('versionInfo', JSON.stringify(versionInfo))
      }
    }

    // 触发事件用户数统计
    const matchCondition = data._id
    Object.assign(matchCondition, {
      create_time: {
        $gte: this.startTime,
        $lte: this.endTime
      }
    })
    if (this.debug) {
      console.log('matchCondition', JSON.stringify(matchCondition))
    }
    const statEventUserRes = await this.aggregate(this.eventLog.tableName, {
      project: {
        appid: 1,
        version: 1,
        platform: 1,
        channel: 1,
        event_key: 1,
        uuid: 1,
        create_time: 1
      },
      match: matchCondition,
      group: [{
        _id: {
          uuid: '$uuid'
        }
      }, {
        _id: {},
        total_users: {
          $sum: 1
        }
      }]
    })

    let eventUserCount = 0
    if (statEventUserRes.data.length > 0) {
      eventUserCount = statEventUserRes.data[0].total_users
    }

    const datetime = new DateTime()
    const insertParams = {
      appid: data._id.appid,
      platform_id: platformInfo._id,
      channel_id: channelInfo._id,
      version_id: versionInfo._id,
      event_key: data._id.event_key,
      event_count: data.event_count,
      user_count: eventUserCount,
      dimension: this.fillType,
      stat_date: datetime.getDate('Ymd', this.startTime),
      start_time: this.startTime,
      end_time: this.endTime
    }
    this.fillData.push(insertParams)
    return insertParams
    // const res = await this.insert(this.tableName, insertParams)
    // if (this.debug) {
    //   console.log('save res', JSON.stringify(res))
    // }
    // if (res && res.id) {
    //   return {
    //     code: 0,
    //     msg: 'have done'
    //   }
    // } else {
    //   console.log(this.tableName + ' log saved failed by params:', JSON.stringify(insertParams))
    //   return {
    //     code: 400,
    //     msg: 'log save error'
    //   }
    // }
  }
}

// 错误结果统计
export class ErrorResult extends BaseMod {
  constructor () {
    super()
    this.tableName = 'stat-error-result'
    this.platforms = []
    this.channels = []
    this.versions = []
    this.errors = []
  }

  // 数据统计
  async stat (type, date, reset) {
    const allowedType = ['day']
    if (!allowedType.includes(type)) {
      return {
        code: 1002,
        msg: 'This type is not allowed'
      }
    }
    this.fillType = type
    const dateTime = new DateTime()
    const dateDimension = dateTime.getTimeDimensionByType(type, -1, date)
    this.startTime = dateDimension.startTime
    this.endTime = dateDimension.endTime

    if (this.debug) {
      console.log('dimension time', this.startTime + '--' + this.endTime)
    }

    // 查看当前时间段日志是否已存在,防止重复生成
    if (!reset) {
      const checkRes = await this.getCollection(this.tableName).where({
        start_time: this.startTime,
        end_time: this.endTime
      }).get()
      if (checkRes.data.length > 0) {
        console.log('error log have existed')
        return {
          code: 1003,
          msg: 'This log have existed'
        }
      }
    } else {
      const delRes = await this.delete(this.tableName, {
        start_time: this.startTime,
        end_time: this.endTime
      })
      console.log('delete old data result:', JSON.stringify(delRes))
    }

    // 数据获取
    this.errorLog = new ErrorLog()
    const statRes = await this.aggregate(this.errorLog.tableName, {
      project: {
        appid: 1,
        version: 1,
        platform: 1,
        channel: 1,
        error_hash: 1,
        create_time: 1
      },
      match: {
        create_time: {
          $gte: this.startTime,
          $lte: this.endTime
        }
      },
      group: {
        _id: {
          appid: '$appid',
          version: '$version',
          platform: '$platform',
          channel: '$channel',
          error_hash: '$error_hash'
        },
        error_count: {
          $sum: 1
        }
      },
      sort: {
        error_count: 1
      },
      getAll: true
    })

    let res = {
      code: 0,
      msg: 'success'
    }
    if (this.debug) {
      console.log('statRes', JSON.stringify(statRes))
    }
    if (statRes.data.length > 0) {
      this.fillData = []
      for (const i in statRes.data) {
        await this.fill(statRes.data[i])
      }

      if (this.fillData.length > 0) {
        res = await this.batchInsert(this.tableName, this.fillData)
      }
    }
    return res
  }

  async fill (data) {
    // 平台信息
    let platformInfo = null
    if (this.platforms && this.platforms[data._id.platform]) {
      platformInfo = this.platforms[data._id.platform]
    } else {
      const platform = new Platform()
      platformInfo = await platform.getPlatformAndCreate(data._id.platform, null)
      if (!platformInfo || platformInfo.length === 0) {
        platformInfo._id = ''
      }
      this.platforms[data._id.platform] = platformInfo
      if (this.debug) {
        console.log('platformInfo', JSON.stringify(platformInfo))
      }
    }

    // 渠道信息
    let channelInfo = null
    const channelKey = data._id.appid + '_' + platformInfo._id + '_' + data._id.channel
    if (this.channels && this.channels[channelKey]) {
      channelInfo = this.channels[channelKey]
    } else {
      const channel = new Channel()
      channelInfo = await channel.getChannelAndCreate(data._id.appid, platformInfo._id, data._id.channel)
      if (!channelInfo || channelInfo.length === 0) {
        channelInfo._id = ''
      }
      this.channels[channelKey] = channelInfo
      if (this.debug) {
        console.log('channelInfo', JSON.stringify(channelInfo))
      }
    }

    // 版本信息
    let versionInfo = null
    const versionKey = data._id.appid + '_' + platformInfo._id + '_' + data._id.version
    if (this.versions && this.versions[versionKey]) {
      versionInfo = this.versions[versionKey]
    } else {
      const version = new Version()
      versionInfo = await version.getVersionAndCreate(data._id.appid, platformInfo._id, data._id.version)
      if (!versionInfo || versionInfo.length === 0) {
        versionInfo._id = ''
      }
      this.versions[versionKey] = versionInfo
      if (this.debug) {
        console.log('versionInfo', JSON.stringify(versionInfo))
      }
    }

    // 错误信息
    let errorInfo = null
    if (this.errors && this.errors[data._id.error_hash]) {
      errorInfo = this.errors[data._id.error_hash]
    } else {
      const cacheKey = 'uni-stat-errors-' + data._id.error_hash
      errorInfo = await this.getCache(cacheKey)
      if (!errorInfo) {
        errorInfo = await this.getCollection(this.errorLog.tableName).where({ error_hash: data._id.error_hash }).limit(1).get()
        if (!errorInfo || errorInfo.data.length === 0) {
          errorInfo.error_msg = ''
        } else {
          errorInfo = errorInfo.data[0]
          await this.setCache(cacheKey, errorInfo)
        }
      }
      this.errors[data._id.error_hash] = errorInfo
    }

    // 最后一次报错事件
    const matchCondition = data._id
    Object.assign(matchCondition, {
      create_time: {
        $gte: this.startTime,
        $lte: this.endTime
      }
    })
    const lastErrorLog = await this.getCollection(this.errorLog.tableName).where(matchCondition).orderBy(
      'create_time', 'desc').limit(1).get()
    let lastErrorTime = ''
    if (lastErrorLog && lastErrorLog.data.length > 0) {
      lastErrorTime = lastErrorLog.data[0].create_time
    }

    const datetime = new DateTime()
    const insertParams = {
      appid: data._id.appid,
      platform_id: platformInfo._id,
      channel_id: channelInfo._id,
      version_id: versionInfo._id,
      hash: data._id.error_hash,
      msg: errorInfo.error_msg,
      count: data.error_count,
      last_time: lastErrorTime,
      dimension: this.fillType,
      stat_date: datetime.getDate('Ymd', this.startTime),
      start_time: this.startTime,
      end_time: this.endTime
    }

    this.fillData.push(insertParams)
    return insertParams
    // const res = await this.insert(this.tableName, insertParams)
    // if (this.debug) {
    //   console.log('save res', JSON.stringify(res))
    // }
    // if (res && res.id) {
    //   return {
    //     code: 0,
    //     msg: 'have done'
    //   }
    // } else {
    //   console.log(this.tableName + ' log saved failed by params:', JSON.stringify(insertParams))
    //   return {
    //     code: 400,
    //     msg: 'log save error'
    //   }
    // }
  }
}

// 用户忠诚度统计
export class Loyalty extends BaseMod {
  constructor () {
    super()
    this.tableName = 'stat-loyalty-result'
    this.platforms = []
    this.channels = []
    this.versions = []
  }

  // 统计
  async stat (type, date, reset) {
    const allowedType = ['day']
    if (!allowedType.includes(type)) {
      return {
        code: 1002,
        msg: 'This type is not allowed'
      }
    }

    this.fillType = type
    const dateTime = new DateTime()
    const dateDimension = dateTime.getTimeDimensionByType(type, -1, date)
    this.startTime = dateDimension.startTime
    this.endTime = dateDimension.endTime

    if (this.debug) {
      console.log('this time', dateTime.getTime())
      console.log('dimension time', this.startTime + '--' + this.endTime)
    }

    // 查看当前时间段日志是否已存在,防止重复生成
    if (!reset) {
      const checkRes = await this.getCollection(this.tableName).where({
        start_time: this.startTime,
        end_time: this.endTime
      }).get()
      if (checkRes.data.length > 0) {
        console.log('loyalty log have existed')
        return {
          code: 1003,
          msg: 'This log have existed'
        }
      }
    } else {
      const delRes = await this.delete(this.tableName, {
        start_time: this.startTime,
        end_time: this.endTime
      })
      console.log('delete old data result:', JSON.stringify(delRes))
    }

    // 数据获取
    this.sessionLog = new SessionLog()
    const statRes = await this.aggregate(this.sessionLog.tableName, {
      project: {
        appid: 1,
        version: 1,
        platform: 1,
        channel: 1,
        page_count: 1,
        duration: 1,
        create_time: 1
      },
      match: {
        create_time: {
          $gte: this.startTime,
          $lte: this.endTime
        }
      },
      group: {
        _id: {
          appid: '$appid',
          version: '$version',
          platform: '$platform',
          channel: '$channel'
        },
        page_count_sum: {
          $sum: '$page_count'
        },
        duration_sum: {
          $sum: '$duration'
        }
      },
      sort: {
        page_count_sum: 1,
        duration_sum: 1
      },
      getAll: true
    })

    let res = {
      code: 0,
      msg: 'success'
    }
    if (this.debug) {
      console.log('statRes', JSON.stringify(statRes))
    }
    if (statRes.data.length > 0) {
      this.fillData = []
      for (const i in statRes.data) {
        await this.fill(statRes.data[i])
      }

      if (this.fillData.length > 0) {
        res = await this.batchInsert(this.tableName, this.fillData)
      }
    }
    return res
  }

  // 数据填充
  async fill (data) {
    // 平台信息
    let platformInfo = null
    if (this.platforms && this.platforms[data._id.platform]) {
      platformInfo = this.platforms[data._id.platform]
    } else {
      const platform = new Platform()
      platformInfo = await platform.getPlatformAndCreate(data._id.platform, null)
      if (!platformInfo || platformInfo.length === 0) {
        platformInfo._id = ''
      }
      this.platforms[data._id.platform] = platformInfo
      if (this.debug) {
        console.log('platformInfo', JSON.stringify(platformInfo))
      }
    }

    // 渠道信息
    let channelInfo = null
    const channelKey = data._id.appid + '_' + platformInfo._id + '_' + data._id.channel
    if (this.channels && this.channels[channelKey]) {
      channelInfo = this.channels[channelKey]
    } else {
      const channel = new Channel()
      channelInfo = await channel.getChannelAndCreate(data._id.appid, platformInfo._id, data._id.channel)
      if (!channelInfo || channelInfo.length === 0) {
        channelInfo._id = ''
      }
      this.channels[channelKey] = channelInfo
      if (this.debug) {
        console.log('channelInfo', JSON.stringify(channelInfo))
      }
    }

    // 版本信息
    let versionInfo = null
    const versionKey = data._id.appid + '_' + platformInfo._id + '_' + data._id.version
    if (this.versions && this.versions[versionKey]) {
      versionInfo = this.versions[versionKey]
    } else {
      const version = new Version()
      versionInfo = await version.getVersionAndCreate(data._id.appid, platformInfo._id, data._id.version)
      if (!versionInfo || versionInfo.length === 0) {
        versionInfo._id = ''
      }
      this.versions[versionKey] = versionInfo
      if (this.debug) {
        console.log('versionInfo', JSON.stringify(versionInfo))
      }
    }

    // 访问深度-用户数统计和访问次数
    const pageMark = [1, 2, 3, 4, [5, 10],
      [10]
    ]
    const matchCondition = Object.assign(data._id, {
      create_time: {
        $gte: this.startTime,
        $lte: this.endTime
      }
    })

    const visitDepthData = {
      visit_users: {},
      visit_times: {}
    }
    for (const pi in pageMark) {
      let pageMarkCondition = {
        page_count: pageMark[pi]
      }

      if (Array.isArray(pageMark[pi])) {
        if (pageMark[pi].length === 2) {
          pageMarkCondition = {
            page_count: {
              $gte: pageMark[pi][0],
              $lte: pageMark[pi][1]
            }
          }
        } else {
          pageMarkCondition = {
            page_count: {
              $gt: pageMark[pi][0]
            }
          }
        }
      }

      // 访问次数(会话次数)统计
      const searchCondition = {
        ...matchCondition,
        ...pageMarkCondition
      }
      const vistRes = await this.aggregate(this.sessionLog.tableName, {
        project: {
          appid: 1,
          version: 1,
          platform: 1,
          channel: 1,
          page_count: 1,
          create_time: 1
        },
        match: searchCondition,
        group: {
          _id: {},
          total_visits: {
            $sum: 1
          }
        }
      })

      if (this.debug) {
        console.log('vistResCondtion', JSON.stringify(searchCondition))
        console.log('vistRes', JSON.stringify(vistRes))
      }
      let vistCount = 0
      if (vistRes.data.length > 0) {
        vistCount = vistRes.data[0].total_visits
      }

      // 用户数统计
      const userRes = await this.aggregate(this.sessionLog.tableName, {
        project: {
          appid: 1,
          version: 1,
          platform: 1,
          channel: 1,
          page_count: 1,
          create_time: 1,
          uuid: 1
        },
        match: searchCondition,
        group: [{
          _id: {
            uuid: '$uuid'
          }
        }, {
          _id: {},
          total_users: {
            $sum: 1
          }
        }]
      })

      if (this.debug) {
        console.log('userResCondtion', JSON.stringify(searchCondition))
        console.log('userRes', JSON.stringify(userRes))
      }

      let userCount = 0
      if (userRes.data.length > 0) {
        userCount = userRes.data[0].total_users
      }

      const pageKey = 'p_' + (Array.isArray(pageMark[pi]) ? pageMark[pi][0] : pageMark[pi])
      visitDepthData.visit_users[pageKey] = userCount
      visitDepthData.visit_times[pageKey] = vistCount
    }

    // 访问时长-用户数统计和访问次数
    const durationMark = [
      [0, 2],
      [3, 5],
      [6, 10],
      [11, 20],
      [21, 30],
      [31, 50],
      [51, 100],
      [100]
    ]
    const durationData = {
      visit_users: {},
      visit_times: {}
    }
    for (const di in durationMark) {
      let durationMarkCondition = {
        duration: durationMark[di]
      }

      if (Array.isArray(durationMark[di])) {
        if (durationMark[di].length === 2) {
          durationMarkCondition = {
            duration: {
              $gte: durationMark[di][0],
              $lte: durationMark[di][1]
            }
          }
        } else {
          durationMarkCondition = {
            duration: {
              $gt: durationMark[di][0]
            }
          }
        }
      }

      // 访问次数(会话次数)统计
      const searchCondition = {
        ...matchCondition,
        ...durationMarkCondition
      }
      if (this.debug) {
        console.log('searchCondition', JSON.stringify(searchCondition))
      }
      const vistRes = await this.aggregate(this.sessionLog.tableName, {
        project: {
          appid: 1,
          version: 1,
          platform: 1,
          channel: 1,
          duration: 1,
          create_time: 1
        },
        match: searchCondition,
        group: {
          _id: {},
          total_visits: {
            $sum: 1
          }
        }
      })

      if (this.debug) {
        console.log('vistRes', JSON.stringify(vistRes))
      }
      let vistCount = 0
      if (vistRes.data.length > 0) {
        vistCount = vistRes.data[0].total_visits
      }

      // 用户数统计
      const userRes = await this.aggregate(this.sessionLog.tableName, {
        project: {
          appid: 1,
          version: 1,
          platform: 1,
          channel: 1,
          uuid: 1,
          duration: 1,
          create_time: 1
        },
        match: searchCondition,
        group: [{
          _id: {
            uuid: '$uuid'
          }
        }, {
          _id: {},
          total_users: {
            $sum: 1
          }
        }]
      })

      if (this.debug) {
        console.log('userRes', JSON.stringify(userRes))
      }

      let userCount = 0
      if (userRes.data.length > 0) {
        userCount = userRes.data[0].total_users
      }

      const pageKey = 's_' + (Array.isArray(durationMark[di]) ? durationMark[di][0] : durationMark[di])
      durationData.visit_users[pageKey] = userCount
      durationData.visit_times[pageKey] = vistCount
    }

    // 数据填充
    const datetime = new DateTime()
    const insertParams = {
      appid: data._id.appid,
      platform_id: platformInfo._id,
      channel_id: channelInfo._id,
      version_id: versionInfo._id,
      visit_depth_data: visitDepthData,
      duration_data: durationData,
      stat_date: datetime.getDate('Ymd', this.startTime),
      start_time: this.startTime,
      end_time: this.endTime
    }

    this.fillData.push(insertParams)
    return insertParams
    // const res = await this.insert(this.tableName, insertParams)
    // if (this.debug) {
    //   console.log('save res', JSON.stringify(res))
    // }
    // if (res && res.id) {
    //   return {
    //     code: 0,
    //     msg: 'have done'
    //   }
    // } else {
    //   console.log(this.tableName + ' log saved failed by params:', JSON.stringify(insertParams))
    //   return {
    //     code: 400,
    //     msg: 'log save error'
    //   }
    // }
  }
}

// 运行错误日志
export class RunErrors extends BaseMod {
  constructor () {
    super()
    this.tableName = 'stat-run-errors'
  }

  async create (params) {
    if (!params) return
    const res = await this.insert(this.tableName, params)
    return res
  }
}

// 统计入口
export default class Stat {
  // 数据上报
  async report (params) {
    let res = {
      code: 0,
      msg: 'success'
    }

    if (!params) {
      return {
        code: 200,
        msg: 'Invild params'
      }
    }

    // 参数解析
    const requestParam = JSON.parse(params.requests)
    if (!requestParam || requestParam.length === 0) {
      return {
        code: 200,
        msg: 'Invild params'
      }
    }

    // 日志填充
    const sessionParams = []; const pageParams = []; const eventParams = []; const errorParams = []
    for (const ri in requestParam) {
      const urlParams = this.parseUrlParams(requestParam[ri])
      if (!urlParams.ak) {
        return {
          code: 201,
          msg: 'Not found appid'
        }
      }

      if (!urlParams.lt) {
        return {
          code: 202,
          msg: 'Not found this log type'
        }
      }

      switch (parseInt(urlParams.lt)) {
        // 会话日志
        case 1: {
          sessionParams.push(urlParams)
          break
        }
        // 页面日志
        case 3:
        case 4:
        case 11: {
          pageParams.push(urlParams)
          break
        }
        // 事件日志
        case 21: {
          eventParams.push(urlParams)
          break
        }
        // 错误日志
        case 31: {
          errorParams.push(urlParams)
          break
        }
        default: {
          console.log('Invalid type by param "lt:' + urlParams.lt + '"')
          break
        }
      }
    }

    try {
      if (sessionParams.length > 0) {
        const sessionLog = new SessionLog()
        res = await sessionLog.batchFill(sessionParams)
      }

      if (pageParams.length > 0) {
        const pageLog = new PageLog()
        res = await pageLog.fill(pageParams)
      }

      if (eventParams.length > 0) {
        const eventLog = new EventLog()
        res = await eventLog.fill(eventParams)
      }

      if (errorParams.length > 0) {
        const errorLog = new ErrorLog()
        res = await errorLog.fill(errorParams)
      }
    } catch (e) {
      res = {
        code: 500,
        msg: 'server error'
      }
      console.log('server error: ' + e)

      const runError = new RunErrors()
      runError.create({
        mod: 'report',
        params: params,
        error: e,
        create_time: new DateTime().getTime()
      })
    }

    return res
  }

  // 解析参数
  parseUrlParams (str) {
    if (!str || typeof str !== 'string') {
      return str
    }
    return str.split('&').reduce((res, cur) => {
      const arr = cur.split('=')
      return Object.assign({
        [arr[0]]: arr[1]
      }, res)
    }, {})
  }

  // 定时触发
  async statCron (context) {
    const baseMod = new BaseMod()
    const dateTime = new DateTime()
    console.log('Cron start time: ', dateTime.getDate('Y-m-d H:i:s'))
    const timeInfo = dateTime.getTimeInfo(null, false)
    // console.log('timeInfo', JSON.stringify(timeInfo))
    const cronConfig = baseMod.getConfig('cron')
    // console.log('cronConfig', JSON.stringify(cronConfig))
    const cronMin = baseMod.getConfig('cronMin')
    const realtimeStat = baseMod.getConfig('realtimeStat')
    // 数据跑批
    let res = null
    if (cronConfig && cronConfig.length > 0) {
      for (var mi in cronConfig) {
        const cronType = cronConfig[mi].type
        const cronTime = cronConfig[mi].time.split(' ')
        if (cronTime.length === 4 && !cronMin) {
          cronTime.splice(3, 1)
        }
        if (baseMod.debug) {
          console.log('cronTime', cronTime)
        }

        if (cronTime.length === 4) {
          if (cronTime[0] !== '*') {
            if (timeInfo.nWeek == cronTime[0] && timeInfo.nHour == cronTime[2] && timeInfo.nMinutes ==
							cronTime[3]) {
              console.log(cronType + '--week run')
              res = await this.stat({
                type: cronType,
                dimension: 'week'
              })
            }
          } else if (cronTime[1] !== '*') {
            if (timeInfo.nDay == cronTime[1] && timeInfo.nHour == cronTime[2] && timeInfo.nMinutes ==
							cronTime[3]) {
              console.log(cronType + '--month run')
              res = await this.stat({
                type: cronType,
                dimension: 'month'
              })
            }
          } else if (cronTime[2] !== '*') {
            if (timeInfo.nHour == cronTime[2] && timeInfo.nMinutes == cronTime[3]) {
              console.log(cronType + '--day run')
              res = await this.stat({
                type: cronType,
                dimension: 'day'
              })
            }
          } else if (cronTime[3] !== '*') {
            if (timeInfo.nMinutes == cronTime[3] && realtimeStat) {
              console.log(cronType + '--hour run')
              res = await this.stat({
                type: cronType,
                dimension: 'hour'
              })
            }
          }
        } else if (cronTime.length === 3) {
          if (cronTime[0] !== '*') {
            if (timeInfo.nWeek == cronTime[0] && timeInfo.nHour == cronTime[2]) {
              console.log(cronType + '--week run')
              res = await this.stat({
                type: cronType,
                dimension: 'week'
              })
            }
          } else if (cronTime[1] !== '*') {
            if (timeInfo.nDay == cronTime[1] && timeInfo.nHour == cronTime[2]) {
              console.log(cronType + '--month run')
              res = await this.stat({
                type: cronType,
                dimension: 'month'
              })
            }
          } else if (cronTime[2] !== '*') {
            if (timeInfo.nHour == cronTime[2]) {
              console.log(cronType + '--day run')
              res = await this.stat({
                type: cronType,
                dimension: 'day'
              })
            }
          } else {
            if (realtimeStat) {
              console.log(cronType + '--hour run')
              res = await this.stat({
                type: cronType,
                dimension: 'hour'
              })
            }
          }
        }
      }
    }
    console.log('Cron end time: ', dateTime.getDate('Y-m-d H:i:s'))
    return {
      code: 0,
      msg: 'Task have done',
      lastCronResult: res
    }
  }

  // 统计相关
  async stat (params) {
    const {
      type,
      dimension,
      date,
      reset
    } = params
    let res = {
      code: 0,
      msg: 'success'
    }

    try {
      switch (type) {
        // 基础统计
        case 'stat': {
          const resultStat = new StatResult()
          res = await resultStat.stat(dimension, date, reset)
          break
        }
        // 周活跃用户表归集
        case 'active-user': {
          const activeUsers = new ActvieUsers()
          res = await activeUsers.stat(date, reset)
          break
        }
        // 留存统计
        case 'retention': {
          const retentionStat = new StatResult()
          res = await retentionStat.retentionStat(dimension, date, reset)
          break
        }
        // 页面统计
        case 'page': {
          const pageStat = new PageResult()
          res = await pageStat.stat(dimension, date, reset)
          break
        }
        // 事件统计
        case 'event': {
          const eventStat = new EventResult()
          res = await eventStat.stat(dimension, date, reset)
          break
        }
        // 错误统计
        case 'error': {
          const errorStat = new ErrorResult()
          res = await errorStat.stat(dimension, date, reset)
          break
        }
        // 用户忠诚度统计
        case 'loyalty': {
          const loyaltyStat = new Loyalty()
          res = await loyaltyStat.stat(dimension, date, reset)
          break
        }
        // 日志清理
        case 'clean': {
          res = await this.cleanLog()
        }
      }
    } catch (e) {
      res = {
        code: 500,
        msg: 'server error'
      }

      if (!this.tryTimes) {
        this.tryTimes = 1
      } else {
        this.tryTimes++
      }

      if (this.tryTimes <= 2) {
        params.reset = true
        res = await this.stat(params)
      } else {
        // 2次尝试失败后返回错误
        console.log('server error: ' + e)

        const runError = new RunErrors()
        runError.create({
          mod: 'stat',
          params: params,
          error: e,
          create_time: new DateTime().getTime()
        })
      }
    }
    return res
  }

  // 日志清理
  async cleanLog () {
    const baseMod = new BaseMod()
    const cleanLog = baseMod.getConfig('cleanLog')
    if (!cleanLog || !cleanLog.open) {
      return {
        code: 100,
        msg: 'The log cleanup service has not been turned on'
      }
    }

    const res = {
      code: 0,
      msg: 'success',
      data: {}
    }

    // 会话日志
    if (cleanLog.reserveDays.sessionLog > 0) {
      const sessionLog = new SessionLog()
      res.data.sessionLog = await sessionLog.clean(cleanLog.reserveDays.sessionLog)
    }

    // 页面日志
    if (cleanLog.reserveDays.pageLog > 0) {
      const pageLog = new PageLog()
      res.data.pageLog = await pageLog.clean(cleanLog.reserveDays.pageLog)
    }

    // 事件日志
    if (cleanLog.reserveDays.eventLog > 0) {
      const eventLog = new EventLog()
      res.data.eventLog = await eventLog.clean(cleanLog.reserveDays.eventLog)
    }

    // 分享日志
    if (cleanLog.reserveDays.shareLog > 0) {
      const shareLog = new ShareLog()
      res.data.shareLog = await shareLog.clean(cleanLog.reserveDays.shareLog)
    }

    // 错误日志
    if (cleanLog.reserveDays.errorLog > 0) {
      const errorLog = new ErrorLog()
      res.data.errorLog = await errorLog.clean(cleanLog.reserveDays.errorLog)
    }

    // 活跃用户日志
    const activeUsersLog = new ActvieUsers()
    res.data.actvieUsersLog = await activeUsersLog.clean()

    // 实时统计日志
    const resultHourLog = new StatResult()
    res.data.resultHourLog = await resultHourLog.clean()

    return res
  }
}
